• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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 
17 package com.android.tools.metalava.cli.signature
18 
19 import com.android.tools.metalava.JDiffXmlWriter
20 import com.android.tools.metalava.OptionsDelegate
21 import com.android.tools.metalava.cli.common.DefaultSignatureFileLoader
22 import com.android.tools.metalava.cli.common.MetalavaSubCommand
23 import com.android.tools.metalava.cli.common.existingFile
24 import com.android.tools.metalava.cli.common.newFile
25 import com.android.tools.metalava.cli.common.progressTracker
26 import com.android.tools.metalava.cli.common.stderr
27 import com.android.tools.metalava.createFilteringVisitorForJDiffWriter
28 import com.android.tools.metalava.createReportFile
29 import com.android.tools.metalava.model.Codebase
30 import com.android.tools.metalava.model.CodebaseFragment
31 import com.android.tools.metalava.model.FilterPredicate
32 import com.android.tools.metalava.model.annotation.DefaultAnnotationManager
33 import com.android.tools.metalava.model.text.FileFormat
34 import com.android.tools.metalava.model.text.SignatureFile
35 import com.android.tools.metalava.model.text.SnapshotDeltaMaker
36 import com.android.tools.metalava.model.visitors.ApiFilters
37 import com.android.tools.metalava.reporter.BasicReporter
38 import com.github.ajalt.clikt.parameters.arguments.argument
39 import com.github.ajalt.clikt.parameters.options.convert
40 import com.github.ajalt.clikt.parameters.options.flag
41 import com.github.ajalt.clikt.parameters.options.option
42 
43 class SignatureToJDiffCommand :
44     MetalavaSubCommand(
45         help =
46             """
47                 Convert an API signature file into a file in the JDiff XML format.
48             """
49                 .trimIndent()
50     ) {
51 
52     private val strip by
53         option(
54                 help =
55                     """
56                         Determines whether types that are not defined within the input signature
57                         file should be stripped from the output or not. This does not include
58                         super class types, i.e. the `extends` attribute in the generated JDiff file.
59                         Historically, they have not been filtered.
60                     """
61                         .trimIndent()
62             )
63             .flag("--no-strip", default = false, defaultForHelp = "false")
64 
65     private val formatForLegacyFiles by
66         option(
67                 "--format-for-legacy-files",
68                 metavar = "<format-specifier>",
69                 help =
70                     """
71                         Optional format to use when reading legacy, i.e. no longer supported, format
72                         versions. Forces the signature file to be parsed as if it was in this
73                         format.
74 
75                         This is provided primarily to allow version 1.0 files, which had no header,
76                         to be parsed as if they were 2.0 files (by specifying
77                         `--format-for-legacy-files=2.0`) so that version 1.0 files can still be read
78                         even though metalava no longer supports version 1.0 files specifically. That
79                         is effectively what metalava did anyway before it removed support for
80                         version 1.0 files so should work reasonably well.
81 
82                         Applies to both `--base-api` and `<api-file>`.
83                     """
84                         .trimIndent()
85             )
86             .convert { specifier -> FileFormat.parseSpecifier(specifier) }
87 
88     private val baseApiFile by
89         option(
90                 "--base-api",
91                 metavar = "<base-api-file>",
92                 help =
93                     """
94                         Optional base API file. If provided then the output will only include API
95                         items that are not in this file.
96                     """
97                         .trimIndent()
98             )
99             .existingFile()
100 
101     private val apiFile by
102         argument(
103                 name = "<api-file>",
104                 help =
105                     """
106                         API signature file to convert to the JDiff XML format.
107                     """
108                         .trimIndent()
109             )
110             .existingFile()
111 
112     private val xmlFile by
113         argument(
114                 name = "<xml-file>",
115                 help =
116                     """
117                         Output JDiff XML format file.
118                     """
119                         .trimIndent()
120             )
121             .newFile()
122 
123     override fun run() {
124         // Make sure that none of the code called by this command accesses the global `options`
125         // property.
126         OptionsDelegate.disallowAccess()
127 
128         val annotationManager = DefaultAnnotationManager()
129         val codebaseConfig =
130             Codebase.Config(
131                 annotationManager = annotationManager,
132                 reporter = BasicReporter(stderr),
133             )
134         val signatureFileLoader =
135             DefaultSignatureFileLoader(
136                 codebaseConfig = codebaseConfig,
137                 formatForLegacyFiles = formatForLegacyFiles,
138             )
139 
140         val signatureApi = signatureFileLoader.load(SignatureFile.fromFiles(apiFile))
141 
142         val strip = strip
143         val apiEmit = FilterPredicate { it.emit }
144         val apiReference = if (strip) apiEmit else FilterPredicate { true }
145         val apiFilters = ApiFilters(emit = apiEmit, reference = apiReference)
146         val baseFile = baseApiFile
147 
148         val signatureFragment =
149             CodebaseFragment.create(signatureApi) { delegate ->
150                 createFilteringVisitorForJDiffWriter(
151                     delegate,
152                     apiFilters = apiFilters,
153                     preFiltered = signatureApi.preFiltered && !strip,
154                     showUnannotated = false,
155                     // Historically, the super class type has not been filtered when generating
156                     // JDiff files, so do not filter here even though it could result in undefined
157                     // types being included in the JDiff file.
158                     filterSuperClassType = false,
159                 )
160             }
161 
162         val outputFragment =
163             if (baseFile != null) {
164                 // Convert base on a diff
165                 val baseApi = signatureFileLoader.load(SignatureFile.fromFiles(baseFile))
166                 SnapshotDeltaMaker.createDelta(baseApi, signatureFragment)
167             } else {
168                 signatureFragment
169             }
170 
171         // See JDiff's XMLToAPI#nameAPI
172         val apiName = xmlFile.nameWithoutExtension.replace(' ', '_')
173         createReportFile(progressTracker, outputFragment, xmlFile, "JDiff File") { printWriter ->
174             JDiffXmlWriter(
175                 writer = printWriter,
176                 apiName = apiName,
177             )
178         }
179     }
180 }
181