Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 062838fc authored by Paul Duffin's avatar Paul Duffin Committed by Android (Google) Code Review
Browse files

Merge "Add ClasspathElement support" into sc-dev

parents 1970d6c5 f23512fa
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ bootstrap_go_package {
    testSrcs: [
        "apex_test.go",
        "bootclasspath_fragment_test.go",
        "classpath_element_test.go",
        "platform_bootclasspath_test.go",
        "systemserver_classpath_fragment_test.go",
        "vndk_test.go",
+317 −0
Original line number Diff line number Diff line
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package apex

import (
	"reflect"
	"testing"

	"android/soong/android"
	"android/soong/java"
	"github.com/google/blueprint"
)

// Contains tests for java.CreateClasspathElements logic from java/classpath_element.go that
// requires apexes.

// testClasspathElementContext is a ClasspathElementContext suitable for use in tests.
type testClasspathElementContext struct {
	testContext *android.TestContext
	module      android.Module
	errs        []error
}

func (t *testClasspathElementContext) OtherModuleHasProvider(module blueprint.Module, provider blueprint.ProviderKey) bool {
	return t.testContext.ModuleHasProvider(module, provider)
}

func (t *testClasspathElementContext) OtherModuleProvider(module blueprint.Module, provider blueprint.ProviderKey) interface{} {
	return t.testContext.ModuleProvider(module, provider)
}

func (t *testClasspathElementContext) ModuleErrorf(fmt string, args ...interface{}) {
	t.errs = append(t.errs, t.testContext.ModuleErrorf(t.module, fmt, args...))
}

var _ java.ClasspathElementContext = (*testClasspathElementContext)(nil)

func TestCreateClasspathElements(t *testing.T) {
	preparer := android.GroupFixturePreparers(
		prepareForTestWithPlatformBootclasspath,
		prepareForTestWithArtApex,
		prepareForTestWithMyapex,
		// For otherapex.
		android.FixtureMergeMockFs(android.MockFS{
			"system/sepolicy/apex/otherapex-file_contexts": nil,
		}),
		java.PrepareForTestWithJavaSdkLibraryFiles,
		java.FixtureWithLastReleaseApis("foo", "othersdklibrary"),
		android.FixtureWithRootAndroidBp(`
		apex {
			name: "com.android.art",
			key: "com.android.art.key",
 			bootclasspath_fragments: [
				"art-bootclasspath-fragment",
			],
			java_libs: [
				"othersdklibrary",
			],
			updatable: false,
		}

		apex_key {
			name: "com.android.art.key",
			public_key: "com.android.art.avbpubkey",
			private_key: "com.android.art.pem",
		}

		bootclasspath_fragment {
			name: "art-bootclasspath-fragment",
			apex_available: [
				"com.android.art",
			],
			contents: [
				"baz",
				"quuz",
			],
		}

		java_library {
			name: "baz",
			apex_available: [
				"com.android.art",
			],
			srcs: ["b.java"],
			installable: true,
		}

		java_library {
			name: "quuz",
			apex_available: [
				"com.android.art",
			],
			srcs: ["b.java"],
			installable: true,
		}

		apex {
			name: "myapex",
			key: "myapex.key",
 			bootclasspath_fragments: [
				"mybootclasspath-fragment",
			],
			java_libs: [
				"othersdklibrary",
			],
			updatable: false,
		}

		apex_key {
			name: "myapex.key",
			public_key: "testkey.avbpubkey",
			private_key: "testkey.pem",
		}

		bootclasspath_fragment {
			name: "mybootclasspath-fragment",
			apex_available: [
				"myapex",
			],
			contents: [
				"bar",
			],
		}

		java_library {
			name: "bar",
			srcs: ["b.java"],
			installable: true,
			apex_available: ["myapex"],
			permitted_packages: ["bar"],
		}

		java_sdk_library {
			name: "foo",
			srcs: ["b.java"],
		}

		java_sdk_library {
			name: "othersdklibrary",
			srcs: ["b.java"],
			shared_library: false,
			apex_available: [
				"com.android.art",
				"myapex",
			],
		}

		bootclasspath_fragment {
			name: "non-apex-fragment",
			contents: ["othersdklibrary"],
		}

		apex {
			name: "otherapex",
			key: "otherapex.key",
			java_libs: [
				"otherapexlibrary",
			],
			updatable: false,
		}

		apex_key {
			name: "otherapex.key",
			public_key: "testkey.avbpubkey",
			private_key: "testkey.pem",
		}

		java_library {
			name: "otherapexlibrary",
			srcs: ["b.java"],
			installable: true,
			apex_available: ["otherapex"],
			permitted_packages: ["otherapexlibrary"],
		}

		platform_bootclasspath {
			name: "myplatform-bootclasspath",

			fragments: [
				{
					apex: "com.android.art",
					module: "art-bootclasspath-fragment",
				},
			],
		}
	`),
	)

	result := preparer.RunTest(t)

	artFragment := result.Module("art-bootclasspath-fragment", "android_common_apex10000")
	artBaz := result.Module("baz", "android_common_apex10000")
	artQuuz := result.Module("quuz", "android_common_apex10000")

	myFragment := result.Module("mybootclasspath-fragment", "android_common_apex10000")
	myBar := result.Module("bar", "android_common_apex10000")

	nonApexFragment := result.Module("non-apex-fragment", "android_common")
	other := result.Module("othersdklibrary", "android_common_apex10000")

	otherApexLibrary := result.Module("otherapexlibrary", "android_common_apex10000")

	platformFoo := result.Module("quuz", "android_common")

	bootclasspath := result.Module("myplatform-bootclasspath", "android_common")

	// Use a custom assertion method instead of AssertDeepEquals as the latter formats the output
	// using %#v which results in meaningless output as ClasspathElements are pointers.
	assertElementsEquals := func(t *testing.T, message string, expected, actual java.ClasspathElements) {
		if !reflect.DeepEqual(expected, actual) {
			t.Errorf("%s: expected:\n  %s\n got:\n  %s", message, expected, actual)
		}
	}

	expectFragmentElement := func(module android.Module, contents ...android.Module) java.ClasspathElement {
		return &java.ClasspathFragmentElement{module, contents}
	}
	expectLibraryElement := func(module android.Module) java.ClasspathElement {
		return &java.ClasspathLibraryElement{module}
	}

	newCtx := func() *testClasspathElementContext {
		return &testClasspathElementContext{testContext: result.TestContext, module: bootclasspath}
	}

	// Verify that CreateClasspathElements works when given valid input.
	t.Run("art:baz, art:quuz, my:bar, foo", func(t *testing.T) {
		ctx := newCtx()
		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, artQuuz, myBar, platformFoo}, []android.Module{artFragment, myFragment})
		expectedElements := java.ClasspathElements{
			expectFragmentElement(artFragment, artBaz, artQuuz),
			expectFragmentElement(myFragment, myBar),
			expectLibraryElement(platformFoo),
		}
		assertElementsEquals(t, "elements", expectedElements, elements)
	})

	// Verify that CreateClasspathElements detects when a fragment does not have an associated apex.
	t.Run("non apex fragment", func(t *testing.T) {
		ctx := newCtx()
		elements := java.CreateClasspathElements(ctx, []android.Module{}, []android.Module{nonApexFragment})
		android.FailIfNoMatchingErrors(t, "fragment non-apex-fragment{.*} is not part of an apex", ctx.errs)
		expectedElements := java.ClasspathElements{}
		assertElementsEquals(t, "elements", expectedElements, elements)
	})

	// Verify that CreateClasspathElements detects when an apex has multiple fragments.
	t.Run("multiple fragments for same apex", func(t *testing.T) {
		ctx := newCtx()
		elements := java.CreateClasspathElements(ctx, []android.Module{}, []android.Module{artFragment, artFragment})
		android.FailIfNoMatchingErrors(t, "apex com.android.art has multiple fragments, art-bootclasspath-fragment{.*} and art-bootclasspath-fragment{.*}", ctx.errs)
		expectedElements := java.ClasspathElements{}
		assertElementsEquals(t, "elements", expectedElements, elements)
	})

	// Verify that CreateClasspathElements detects when a library is in multiple fragments.
	t.Run("library from multiple fragments", func(t *testing.T) {
		ctx := newCtx()
		elements := java.CreateClasspathElements(ctx, []android.Module{other}, []android.Module{artFragment, myFragment})
		android.FailIfNoMatchingErrors(t, "library othersdklibrary{.*} is in two separate fragments, art-bootclasspath-fragment{.*} and mybootclasspath-fragment{.*}", ctx.errs)
		expectedElements := java.ClasspathElements{}
		assertElementsEquals(t, "elements", expectedElements, elements)
	})

	// Verify that CreateClasspathElements detects when a fragment's contents are not contiguous and
	// are separated by a library from another fragment.
	t.Run("discontiguous separated by fragment", func(t *testing.T) {
		ctx := newCtx()
		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, myBar, artQuuz, platformFoo}, []android.Module{artFragment, myFragment})
		expectedElements := java.ClasspathElements{
			expectFragmentElement(artFragment, artBaz, artQuuz),
			expectFragmentElement(myFragment, myBar),
			expectLibraryElement(platformFoo),
		}
		assertElementsEquals(t, "elements", expectedElements, elements)
		android.FailIfNoMatchingErrors(t, "libraries from the same fragment must be contiguous, however baz{.*} and quuz{os:android,arch:common,apex:apex10000} from fragment art-bootclasspath-fragment{.*} are separated by libraries from fragment mybootclasspath-fragment{.*} like bar{.*}", ctx.errs)
	})

	// Verify that CreateClasspathElements detects when a fragment's contents are not contiguous and
	// are separated by a standalone library.
	t.Run("discontiguous separated by library", func(t *testing.T) {
		ctx := newCtx()
		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, platformFoo, artQuuz, myBar}, []android.Module{artFragment, myFragment})
		expectedElements := java.ClasspathElements{
			expectFragmentElement(artFragment, artBaz, artQuuz),
			expectLibraryElement(platformFoo),
			expectFragmentElement(myFragment, myBar),
		}
		assertElementsEquals(t, "elements", expectedElements, elements)
		android.FailIfNoMatchingErrors(t, "libraries from the same fragment must be contiguous, however baz{.*} and quuz{os:android,arch:common,apex:apex10000} from fragment art-bootclasspath-fragment{.*} are separated by library quuz{.*}", ctx.errs)
	})

	// Verify that CreateClasspathElements detects when there a library on the classpath that
	// indicates it is from an apex the supplied fragments list does not contain a fragment for that
	// apex.
	t.Run("no fragment for apex", func(t *testing.T) {
		ctx := newCtx()
		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, otherApexLibrary}, []android.Module{artFragment})
		expectedElements := java.ClasspathElements{
			expectFragmentElement(artFragment, artBaz),
		}
		assertElementsEquals(t, "elements", expectedElements, elements)
		android.FailIfNoMatchingErrors(t, `library otherapexlibrary{.*} is from apexes \[otherapex\] which have no corresponding fragment in \[art-bootclasspath-fragment{.*}\]`, ctx.errs)
	})
}
+1 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ bootstrap_go_package {
        "bootclasspath.go",
        "bootclasspath_fragment.go",
        "builder.go",
        "classpath_element.go",
        "classpath_fragment.go",
        "device_host_converter.go",
        "dex.go",
+229 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package java

import (
	"fmt"
	"strings"

	"android/soong/android"
	"github.com/google/blueprint"
)

// Supports constructing a list of ClasspathElement from a set of fragments and modules.

// ClasspathElement represents a component that contributes to a classpath. That can be
// either a java module or a classpath fragment module.
type ClasspathElement interface {
	Module() android.Module
	String() string
}

type ClasspathElements []ClasspathElement

// ClasspathFragmentElement is a ClasspathElement that encapsulates a classpath fragment module.
type ClasspathFragmentElement struct {
	Fragment android.Module
	Contents []android.Module
}

func (b *ClasspathFragmentElement) Module() android.Module {
	return b.Fragment
}

func (b *ClasspathFragmentElement) String() string {
	contents := []string{}
	for _, module := range b.Contents {
		contents = append(contents, module.String())
	}
	return fmt.Sprintf("fragment(%s, %s)", b.Fragment, strings.Join(contents, ", "))
}

var _ ClasspathElement = (*ClasspathFragmentElement)(nil)

// ClasspathLibraryElement is a ClasspathElement that encapsulates a java library.
type ClasspathLibraryElement struct {
	Library android.Module
}

func (b *ClasspathLibraryElement) Module() android.Module {
	return b.Library
}

func (b *ClasspathLibraryElement) String() string {
	return fmt.Sprintf("library{%s}", b.Library)
}

var _ ClasspathElement = (*ClasspathLibraryElement)(nil)

// ClasspathElementContext defines the context methods needed by CreateClasspathElements
type ClasspathElementContext interface {
	OtherModuleHasProvider(m blueprint.Module, provider blueprint.ProviderKey) bool
	OtherModuleProvider(m blueprint.Module, provider blueprint.ProviderKey) interface{}
	ModuleErrorf(fmt string, args ...interface{})
}

// CreateClasspathElements creates a list of ClasspathElement objects from a list of libraries and
// a list of fragments.
//
// The libraries parameter contains the set of libraries from which the classpath is constructed.
// The fragments parameter contains the classpath fragment modules whose contents are libraries that
// are part of the classpath. Each library in the libraries parameter may be part of a fragment. The
// determination as to which libraries belong to fragments and which do not is based on the apex to
// which they belong, if any.
//
// Every fragment in the fragments list must be part of one or more apexes and each apex is assumed
// to contain only a single fragment from the fragments list. A library in the libraries parameter
// that is part of an apex must be provided by a classpath fragment in the corresponding apex.
//
// This will return a ClasspathElements list that contains a ClasspathElement for each standalone
// library and each fragment. The order of the elements in the list is such that if the list was
// flattened into a list of library modules that it would result in the same list or modules as the
// input libraries. Flattening the list can be done by replacing each ClasspathFragmentElement in
// the list with its Contents field.
//
// Requirements/Assumptions:
// * A fragment can be associated with more than one apex but each apex must only be associated with
//   a single fragment from the fragments list.
// * All of a fragment's contents must appear as a contiguous block in the same order in the
//   libraries list.
// * Each library must only appear in a single fragment.
//
// The apex is used to identify which libraries belong to which fragment. First a mapping is created
// from apex to fragment. Then the libraries are iterated over and any library in an apex is
// associated with an element for the fragment to which it belongs. Otherwise, the libraries are
// standalone and have their own element.
//
// e.g. Given the following input:
//     libraries: com.android.art:core-oj, com.android.art:core-libart, framework, ext
//     fragments: com.android.art:art-bootclasspath-fragment
//
// Then this will return:
//     ClasspathFragmentElement(art-bootclasspath-fragment, [core-oj, core-libart]),
//     ClasspathLibraryElement(framework),
//     ClasspathLibraryElement(ext),
func CreateClasspathElements(ctx ClasspathElementContext, libraries []android.Module, fragments []android.Module) ClasspathElements {
	// Create a map from apex name to the fragment module. This makes it easy to find the fragment
	// associated with a particular apex.
	apexToFragment := map[string]android.Module{}
	for _, fragment := range fragments {
		if !ctx.OtherModuleHasProvider(fragment, android.ApexInfoProvider) {
			ctx.ModuleErrorf("fragment %s is not part of an apex", fragment)
			continue
		}

		apexInfo := ctx.OtherModuleProvider(fragment, android.ApexInfoProvider).(android.ApexInfo)
		for _, apex := range apexInfo.InApexVariants {
			if existing, ok := apexToFragment[apex]; ok {
				ctx.ModuleErrorf("apex %s has multiple fragments, %s and %s", apex, fragment, existing)
				continue
			}
			apexToFragment[apex] = fragment
		}
	}

	fragmentToElement := map[android.Module]*ClasspathFragmentElement{}
	elements := []ClasspathElement{}
	var currentElement ClasspathElement

skipLibrary:
	// Iterate over the libraries to construct the ClasspathElements list.
	for _, library := range libraries {
		var element ClasspathElement
		if ctx.OtherModuleHasProvider(library, android.ApexInfoProvider) {
			apexInfo := ctx.OtherModuleProvider(library, android.ApexInfoProvider).(android.ApexInfo)

			var fragment android.Module

			// Make sure that the library is in only one fragment of the classpath.
			for _, apex := range apexInfo.InApexVariants {
				if f, ok := apexToFragment[apex]; ok {
					if fragment == nil {
						// This is the first fragment so just save it away.
						fragment = f
					} else if f != fragment {
						// This apex variant of the library is in a different fragment.
						ctx.ModuleErrorf("library %s is in two separate fragments, %s and %s", library, fragment, f)
						// Skip over this library entirely as otherwise the resulting classpath elements would
						// be invalid.
						continue skipLibrary
					}
				} else {
					// There is no fragment associated with the library's apex.
				}
			}

			if fragment == nil {
				ctx.ModuleErrorf("library %s is from apexes %s which have no corresponding fragment in %s",
					library, apexInfo.InApexVariants, fragments)
				// Skip over this library entirely as otherwise the resulting classpath elements would
				// be invalid.
				continue skipLibrary
			} else if existingFragmentElement, ok := fragmentToElement[fragment]; ok {
				// This library is in a fragment element that has already been added.

				// If the existing fragment element is still the current element then this library is
				// contiguous with other libraries in that fragment so there is nothing more to do.
				// Otherwise this library is not contiguous with other libraries in the same fragment which
				// is an error.
				if existingFragmentElement != currentElement {
					separator := ""
					if fragmentElement, ok := currentElement.(*ClasspathFragmentElement); ok {
						separator = fmt.Sprintf("libraries from fragment %s like %s", fragmentElement.Fragment, fragmentElement.Contents[0])
					} else {
						libraryElement := currentElement.(*ClasspathLibraryElement)
						separator = fmt.Sprintf("library %s", libraryElement.Library)
					}

					// Get the library that precedes this library in the fragment. That is the last library as
					// this library has not yet been added.
					precedingLibraryInFragment := existingFragmentElement.Contents[len(existingFragmentElement.Contents)-1]
					ctx.ModuleErrorf("libraries from the same fragment must be contiguous, however %s and %s from fragment %s are separated by %s",
						precedingLibraryInFragment, library, fragment, separator)
				}

				// Add this library to the fragment element's contents.
				existingFragmentElement.Contents = append(existingFragmentElement.Contents, library)
			} else {
				// This is the first library in this fragment so add a new element for the fragment,
				// including the library.
				fragmentElement := &ClasspathFragmentElement{
					Fragment: fragment,
					Contents: []android.Module{library},
				}

				// Store it away so we can detect when attempting to create another element for the same
				// fragment.
				fragmentToElement[fragment] = fragmentElement
				element = fragmentElement
			}
		} else {
			// The library is from the platform so just add an element for it.
			element = &ClasspathLibraryElement{Library: library}
		}

		// If no element was created then it means that the library has been added to an existing
		// fragment element so the list of elements and current element are unaffected.
		if element != nil {
			// Add the element to the list and make it the current element for the next iteration.
			elements = append(elements, element)
			currentElement = element
		}
	}

	return elements
}