1/* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package java 18 19import ( 20 "fmt" 21 "strings" 22 23 "android/soong/android" 24 "github.com/google/blueprint" 25) 26 27// Supports constructing a list of ClasspathElement from a set of fragments and modules. 28 29// ClasspathElement represents a component that contributes to a classpath. That can be 30// either a java module or a classpath fragment module. 31type ClasspathElement interface { 32 Module() android.Module 33 String() string 34} 35 36type ClasspathElements []ClasspathElement 37 38// ClasspathFragmentElement is a ClasspathElement that encapsulates a classpath fragment module. 39type ClasspathFragmentElement struct { 40 Fragment android.Module 41 Contents []android.Module 42} 43 44func (b *ClasspathFragmentElement) Module() android.Module { 45 return b.Fragment 46} 47 48func (b *ClasspathFragmentElement) String() string { 49 contents := []string{} 50 for _, module := range b.Contents { 51 contents = append(contents, module.String()) 52 } 53 return fmt.Sprintf("fragment(%s, %s)", b.Fragment, strings.Join(contents, ", ")) 54} 55 56var _ ClasspathElement = (*ClasspathFragmentElement)(nil) 57 58// ClasspathLibraryElement is a ClasspathElement that encapsulates a java library. 59type ClasspathLibraryElement struct { 60 Library android.Module 61} 62 63func (b *ClasspathLibraryElement) Module() android.Module { 64 return b.Library 65} 66 67func (b *ClasspathLibraryElement) String() string { 68 return fmt.Sprintf("library{%s}", b.Library) 69} 70 71var _ ClasspathElement = (*ClasspathLibraryElement)(nil) 72 73// ClasspathElementContext defines the context methods needed by CreateClasspathElements 74type ClasspathElementContext interface { 75 OtherModuleHasProvider(m blueprint.Module, provider blueprint.ProviderKey) bool 76 OtherModuleProvider(m blueprint.Module, provider blueprint.ProviderKey) interface{} 77 ModuleErrorf(fmt string, args ...interface{}) 78} 79 80// CreateClasspathElements creates a list of ClasspathElement objects from a list of libraries and 81// a list of fragments. 82// 83// The libraries parameter contains the set of libraries from which the classpath is constructed. 84// The fragments parameter contains the classpath fragment modules whose contents are libraries that 85// are part of the classpath. Each library in the libraries parameter may be part of a fragment. The 86// determination as to which libraries belong to fragments and which do not is based on the apex to 87// which they belong, if any. 88// 89// Every fragment in the fragments list must be part of one or more apexes and each apex is assumed 90// to contain only a single fragment from the fragments list. A library in the libraries parameter 91// that is part of an apex must be provided by a classpath fragment in the corresponding apex. 92// 93// This will return a ClasspathElements list that contains a ClasspathElement for each standalone 94// library and each fragment. The order of the elements in the list is such that if the list was 95// flattened into a list of library modules that it would result in the same list or modules as the 96// input libraries. Flattening the list can be done by replacing each ClasspathFragmentElement in 97// the list with its Contents field. 98// 99// Requirements/Assumptions: 100// * A fragment can be associated with more than one apex but each apex must only be associated with 101// a single fragment from the fragments list. 102// * All of a fragment's contents must appear as a contiguous block in the same order in the 103// libraries list. 104// * Each library must only appear in a single fragment. 105// 106// The apex is used to identify which libraries belong to which fragment. First a mapping is created 107// from apex to fragment. Then the libraries are iterated over and any library in an apex is 108// associated with an element for the fragment to which it belongs. Otherwise, the libraries are 109// standalone and have their own element. 110// 111// e.g. Given the following input: 112// libraries: com.android.art:core-oj, com.android.art:core-libart, framework, ext 113// fragments: com.android.art:art-bootclasspath-fragment 114// 115// Then this will return: 116// ClasspathFragmentElement(art-bootclasspath-fragment, [core-oj, core-libart]), 117// ClasspathLibraryElement(framework), 118// ClasspathLibraryElement(ext), 119func CreateClasspathElements(ctx ClasspathElementContext, libraries []android.Module, fragments []android.Module) ClasspathElements { 120 // Create a map from apex name to the fragment module. This makes it easy to find the fragment 121 // associated with a particular apex. 122 apexToFragment := map[string]android.Module{} 123 for _, fragment := range fragments { 124 if !ctx.OtherModuleHasProvider(fragment, android.ApexInfoProvider) { 125 ctx.ModuleErrorf("fragment %s is not part of an apex", fragment) 126 continue 127 } 128 129 apexInfo := ctx.OtherModuleProvider(fragment, android.ApexInfoProvider).(android.ApexInfo) 130 for _, apex := range apexInfo.InApexVariants { 131 if existing, ok := apexToFragment[apex]; ok { 132 ctx.ModuleErrorf("apex %s has multiple fragments, %s and %s", apex, fragment, existing) 133 continue 134 } 135 apexToFragment[apex] = fragment 136 } 137 } 138 139 fragmentToElement := map[android.Module]*ClasspathFragmentElement{} 140 elements := []ClasspathElement{} 141 var currentElement ClasspathElement 142 143skipLibrary: 144 // Iterate over the libraries to construct the ClasspathElements list. 145 for _, library := range libraries { 146 var element ClasspathElement 147 if ctx.OtherModuleHasProvider(library, android.ApexInfoProvider) { 148 apexInfo := ctx.OtherModuleProvider(library, android.ApexInfoProvider).(android.ApexInfo) 149 150 var fragment android.Module 151 152 // Make sure that the library is in only one fragment of the classpath. 153 for _, apex := range apexInfo.InApexVariants { 154 if f, ok := apexToFragment[apex]; ok { 155 if fragment == nil { 156 // This is the first fragment so just save it away. 157 fragment = f 158 } else if f != fragment { 159 // This apex variant of the library is in a different fragment. 160 ctx.ModuleErrorf("library %s is in two separate fragments, %s and %s", library, fragment, f) 161 // Skip over this library entirely as otherwise the resulting classpath elements would 162 // be invalid. 163 continue skipLibrary 164 } 165 } else { 166 // There is no fragment associated with the library's apex. 167 } 168 } 169 170 if fragment == nil { 171 ctx.ModuleErrorf("library %s is from apexes %s which have no corresponding fragment in %s", 172 library, apexInfo.InApexVariants, fragments) 173 // Skip over this library entirely as otherwise the resulting classpath elements would 174 // be invalid. 175 continue skipLibrary 176 } else if existingFragmentElement, ok := fragmentToElement[fragment]; ok { 177 // This library is in a fragment element that has already been added. 178 179 // If the existing fragment element is still the current element then this library is 180 // contiguous with other libraries in that fragment so there is nothing more to do. 181 // Otherwise this library is not contiguous with other libraries in the same fragment which 182 // is an error. 183 if existingFragmentElement != currentElement { 184 separator := "" 185 if fragmentElement, ok := currentElement.(*ClasspathFragmentElement); ok { 186 separator = fmt.Sprintf("libraries from fragment %s like %s", fragmentElement.Fragment, fragmentElement.Contents[0]) 187 } else { 188 libraryElement := currentElement.(*ClasspathLibraryElement) 189 separator = fmt.Sprintf("library %s", libraryElement.Library) 190 } 191 192 // Get the library that precedes this library in the fragment. That is the last library as 193 // this library has not yet been added. 194 precedingLibraryInFragment := existingFragmentElement.Contents[len(existingFragmentElement.Contents)-1] 195 ctx.ModuleErrorf("libraries from the same fragment must be contiguous, however %s and %s from fragment %s are separated by %s", 196 precedingLibraryInFragment, library, fragment, separator) 197 } 198 199 // Add this library to the fragment element's contents. 200 existingFragmentElement.Contents = append(existingFragmentElement.Contents, library) 201 } else { 202 // This is the first library in this fragment so add a new element for the fragment, 203 // including the library. 204 fragmentElement := &ClasspathFragmentElement{ 205 Fragment: fragment, 206 Contents: []android.Module{library}, 207 } 208 209 // Store it away so we can detect when attempting to create another element for the same 210 // fragment. 211 fragmentToElement[fragment] = fragmentElement 212 element = fragmentElement 213 } 214 } else { 215 // The library is from the platform so just add an element for it. 216 element = &ClasspathLibraryElement{Library: library} 217 } 218 219 // If no element was created then it means that the library has been added to an existing 220 // fragment element so the list of elements and current element are unaffected. 221 if element != nil { 222 // Add the element to the list and make it the current element for the next iteration. 223 elements = append(elements, element) 224 currentElement = element 225 } 226 } 227 228 return elements 229} 230