| /* |
| * 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.AnyProviderKey) bool |
| OtherModuleProvider(m blueprint.Module, provider blueprint.AnyProviderKey) 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 |
| } |