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// 113// libraries: com.android.art:core-oj, com.android.art:core-libart, framework, ext 114// fragments: com.android.art:art-bootclasspath-fragment 115// 116// Then this will return: 117// 118// ClasspathFragmentElement(art-bootclasspath-fragment, [core-oj, core-libart]), 119// ClasspathLibraryElement(framework), 120// ClasspathLibraryElement(ext), 121func CreateClasspathElements(ctx ClasspathElementContext, libraries []android.Module, fragments []android.Module) ClasspathElements { 122 // Create a map from apex name to the fragment module. This makes it easy to find the fragment 123 // associated with a particular apex. 124 apexToFragment := map[string]android.Module{} 125 for _, fragment := range fragments { 126 if !ctx.OtherModuleHasProvider(fragment, android.ApexInfoProvider) { 127 ctx.ModuleErrorf("fragment %s is not part of an apex", fragment) 128 continue 129 } 130 131 apexInfo := ctx.OtherModuleProvider(fragment, android.ApexInfoProvider).(android.ApexInfo) 132 for _, apex := range apexInfo.InApexVariants { 133 if existing, ok := apexToFragment[apex]; ok { 134 ctx.ModuleErrorf("apex %s has multiple fragments, %s and %s", apex, fragment, existing) 135 continue 136 } 137 apexToFragment[apex] = fragment 138 } 139 } 140 141 fragmentToElement := map[android.Module]*ClasspathFragmentElement{} 142 elements := []ClasspathElement{} 143 var currentElement ClasspathElement 144 145skipLibrary: 146 // Iterate over the libraries to construct the ClasspathElements list. 147 for _, library := range libraries { 148 var element ClasspathElement 149 if ctx.OtherModuleHasProvider(library, android.ApexInfoProvider) { 150 apexInfo := ctx.OtherModuleProvider(library, android.ApexInfoProvider).(android.ApexInfo) 151 152 var fragment android.Module 153 154 // Make sure that the library is in only one fragment of the classpath. 155 for _, apex := range apexInfo.InApexVariants { 156 if f, ok := apexToFragment[apex]; ok { 157 if fragment == nil { 158 // This is the first fragment so just save it away. 159 fragment = f 160 } else if f != fragment { 161 // This apex variant of the library is in a different fragment. 162 ctx.ModuleErrorf("library %s is in two separate fragments, %s and %s", library, fragment, f) 163 // Skip over this library entirely as otherwise the resulting classpath elements would 164 // be invalid. 165 continue skipLibrary 166 } 167 } else { 168 // There is no fragment associated with the library's apex. 169 } 170 } 171 172 if fragment == nil { 173 ctx.ModuleErrorf("library %s is from apexes %s which have no corresponding fragment in %s", 174 library, apexInfo.InApexVariants, fragments) 175 // Skip over this library entirely as otherwise the resulting classpath elements would 176 // be invalid. 177 continue skipLibrary 178 } else if existingFragmentElement, ok := fragmentToElement[fragment]; ok { 179 // This library is in a fragment element that has already been added. 180 181 // If the existing fragment element is still the current element then this library is 182 // contiguous with other libraries in that fragment so there is nothing more to do. 183 // Otherwise this library is not contiguous with other libraries in the same fragment which 184 // is an error. 185 if existingFragmentElement != currentElement { 186 separator := "" 187 if fragmentElement, ok := currentElement.(*ClasspathFragmentElement); ok { 188 separator = fmt.Sprintf("libraries from fragment %s like %s", fragmentElement.Fragment, fragmentElement.Contents[0]) 189 } else { 190 libraryElement := currentElement.(*ClasspathLibraryElement) 191 separator = fmt.Sprintf("library %s", libraryElement.Library) 192 } 193 194 // Get the library that precedes this library in the fragment. That is the last library as 195 // this library has not yet been added. 196 precedingLibraryInFragment := existingFragmentElement.Contents[len(existingFragmentElement.Contents)-1] 197 ctx.ModuleErrorf("libraries from the same fragment must be contiguous, however %s and %s from fragment %s are separated by %s", 198 precedingLibraryInFragment, library, fragment, separator) 199 } 200 201 // Add this library to the fragment element's contents. 202 existingFragmentElement.Contents = append(existingFragmentElement.Contents, library) 203 } else { 204 // This is the first library in this fragment so add a new element for the fragment, 205 // including the library. 206 fragmentElement := &ClasspathFragmentElement{ 207 Fragment: fragment, 208 Contents: []android.Module{library}, 209 } 210 211 // Store it away so we can detect when attempting to create another element for the same 212 // fragment. 213 fragmentToElement[fragment] = fragmentElement 214 element = fragmentElement 215 } 216 } else { 217 // The library is from the platform so just add an element for it. 218 element = &ClasspathLibraryElement{Library: library} 219 } 220 221 // If no element was created then it means that the library has been added to an existing 222 // fragment element so the list of elements and current element are unaffected. 223 if element != nil { 224 // Add the element to the list and make it the current element for the next iteration. 225 elements = append(elements, element) 226 currentElement = element 227 } 228 } 229 230 return elements 231} 232