platform_build_soong/java/classpath_element.go
Paul Duffin f23512fa58 Add ClasspathElement support
The configuration of the bootclasspath, e.g. PRODUCT_BOOT_JARS,
includes libraries that are standalone and libraries that are part of
an APEX. All libraries that are part of an APEX must also be part of a
bootclasspath_fragment. Currently, the libraries and fragments are
handled separately but that is limiting as it does not make it easy to
process all the libraries while treating those from fragments
differently to standalone libraries. That is needed for hidden API
processing as it needs to use classesJars from standalone libraries
but pre-generated flag files from fragments.

This change adds support for ClasspathElements which is represents the
classpath as a whole while differentiating between standalone libraries
and fragments.

Bug: 177892522
Test: m nothing
Merged-In: I7a4adc67164a46079eb8ec0a17fc755044b4949d
Change-Id: I7a4adc67164a46079eb8ec0a17fc755044b4949d
(cherry picked from commit e245b61aa2)
2021-06-18 21:02:10 +01:00

229 lines
9.3 KiB
Go

/*
* 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
}