diff options
Diffstat (limited to 'java')
| -rw-r--r-- | java/androidmk.go | 5 | ||||
| -rw-r--r-- | java/app_import.go | 26 | ||||
| -rw-r--r-- | java/base.go | 17 | ||||
| -rw-r--r-- | java/bootclasspath_fragment.go | 23 | ||||
| -rw-r--r-- | java/bootclasspath_fragment_test.go | 114 | ||||
| -rw-r--r-- | java/classpath_element.go | 22 | ||||
| -rw-r--r-- | java/dexpreopt_bootjars.go | 1 | ||||
| -rw-r--r-- | java/droiddoc.go | 10 | ||||
| -rw-r--r-- | java/droidstubs.go | 197 | ||||
| -rw-r--r-- | java/genrule.go | 32 | ||||
| -rw-r--r-- | java/hiddenapi_modular.go | 157 | ||||
| -rw-r--r-- | java/java.go | 8 | ||||
| -rw-r--r-- | java/java_test.go | 35 | ||||
| -rw-r--r-- | java/lint.go | 16 | ||||
| -rw-r--r-- | java/platform_bootclasspath.go | 4 | ||||
| -rw-r--r-- | java/platform_compat_config.go | 2 | ||||
| -rw-r--r-- | java/sdk_library.go | 13 | ||||
| -rw-r--r-- | java/sdk_library_external.go | 7 |
18 files changed, 457 insertions, 232 deletions
diff --git a/java/androidmk.go b/java/androidmk.go index 4cf5ee49e..75ac0e72d 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -167,9 +167,8 @@ func (j *Test) AndroidMkEntries() []android.AndroidMkEntries { entries.SetString("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", "true") } entries.AddStrings("LOCAL_TEST_MAINLINE_MODULES", j.testProperties.Test_mainline_modules...) - if Bool(j.testProperties.Test_options.Unit_test) { - entries.SetBool("LOCAL_IS_UNIT_TEST", true) - } + + j.testProperties.Test_options.CommonTestOptions.SetAndroidMkEntries(entries) }) return entriesList diff --git a/java/app_import.go b/java/app_import.go index 4bab14b32..d6dca3836 100644 --- a/java/app_import.go +++ b/java/app_import.go @@ -461,19 +461,19 @@ func createVariantGroupType(variants []string, variantGroupName string) reflect. // android_app_import imports a prebuilt apk with additional processing specified in the module. // DPI-specific apk source files can be specified using dpi_variants. Example: // -// android_app_import { -// name: "example_import", -// apk: "prebuilts/example.apk", -// dpi_variants: { -// mdpi: { -// apk: "prebuilts/example_mdpi.apk", -// }, -// xhdpi: { -// apk: "prebuilts/example_xhdpi.apk", -// }, -// }, -// presigned: true, -// } +// android_app_import { +// name: "example_import", +// apk: "prebuilts/example.apk", +// dpi_variants: { +// mdpi: { +// apk: "prebuilts/example_mdpi.apk", +// }, +// xhdpi: { +// apk: "prebuilts/example_xhdpi.apk", +// }, +// }, +// presigned: true, +// } func AndroidAppImportFactory() android.Module { module := &AndroidAppImport{} module.AddProperties(&module.properties) diff --git a/java/base.go b/java/base.go index 94daf37fc..fe92b4754 100644 --- a/java/base.go +++ b/java/base.go @@ -267,6 +267,9 @@ type DeviceProperties struct { // Only for libraries created by a sysprop_library module, SyspropPublicStub is the name of the // public stubs library. SyspropPublicStub string `blueprint:"mutated"` + + HiddenAPIPackageProperties + HiddenAPIFlagFileProperties } // Device properties that can be overridden by overriding module (e.g. override_android_app) @@ -564,6 +567,20 @@ func (j *Module) addHostAndDeviceProperties() { ) } +// provideHiddenAPIPropertyInfo populates a HiddenAPIPropertyInfo from hidden API properties and +// makes it available through the hiddenAPIPropertyInfoProvider. +func (j *Module) provideHiddenAPIPropertyInfo(ctx android.ModuleContext) { + hiddenAPIInfo := newHiddenAPIPropertyInfo() + + // Populate with flag file paths from the properties. + hiddenAPIInfo.extractFlagFilesFromProperties(ctx, &j.deviceProperties.HiddenAPIFlagFileProperties) + + // Populate with package rules from the properties. + hiddenAPIInfo.extractPackageRulesFromProperties(&j.deviceProperties.HiddenAPIPackageProperties) + + ctx.SetProvider(hiddenAPIPropertyInfoProvider, hiddenAPIInfo) +} + func (j *Module) OutputFiles(tag string) (android.Paths, error) { switch tag { case "": diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go index 56401b3f7..93168070e 100644 --- a/java/bootclasspath_fragment.go +++ b/java/bootclasspath_fragment.go @@ -128,7 +128,7 @@ type bootclasspathFragmentProperties struct { Coverage BootclasspathFragmentCoverageAffectedProperties // Hidden API related properties. - Hidden_api HiddenAPIFlagFileProperties + HiddenAPIFlagFileProperties // The list of additional stub libraries which this fragment's contents use but which are not // provided by another bootclasspath_fragment. @@ -145,7 +145,7 @@ type bootclasspathFragmentProperties struct { BootclasspathFragmentsDepsProperties } -type HiddenApiPackageProperties struct { +type HiddenAPIPackageProperties struct { Hidden_api struct { // Contains prefixes of a package hierarchy that is provided solely by this // bootclasspath_fragment. @@ -222,8 +222,8 @@ type HiddenApiPackageProperties struct { } type SourceOnlyBootclasspathProperties struct { - HiddenApiPackageProperties - Coverage HiddenApiPackageProperties + HiddenAPIPackageProperties + Coverage HiddenAPIPackageProperties } type BootclasspathFragmentModule struct { @@ -293,7 +293,7 @@ func bootclasspathFragmentFactory() android.Module { return } - err = proptools.AppendProperties(&m.sourceOnlyProperties.HiddenApiPackageProperties, &m.sourceOnlyProperties.Coverage, nil) + err = proptools.AppendProperties(&m.sourceOnlyProperties.HiddenAPIPackageProperties, &m.sourceOnlyProperties.Coverage, nil) if err != nil { ctx.PropertyErrorf("coverage", "error trying to append hidden api coverage specific properties: %s", err) return @@ -825,7 +825,12 @@ func (b *BootclasspathFragmentModule) createHiddenAPIFlagInput(ctx android.Modul input.gatherStubLibInfo(ctx, contents) // Populate with flag file paths from the properties. - input.extractFlagFilesFromProperties(ctx, &b.properties.Hidden_api) + input.extractFlagFilesFromProperties(ctx, &b.properties.HiddenAPIFlagFileProperties) + + // Populate with package rules from the properties. + input.extractPackageRulesFromProperties(&b.sourceOnlyProperties.HiddenAPIPackageProperties) + + input.gatherPropertyInfo(ctx, contents) // Add the stub dex jars from this module's fragment dependencies. input.DependencyStubDexJarsByScope.addStubDexJarsByModule(dependencyHiddenApiInfo.TransitiveStubDexJarsByScope) @@ -862,9 +867,9 @@ func (b *BootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleC // If the module specifies split_packages or package_prefixes then use those to generate the // signature patterns. - splitPackages := b.sourceOnlyProperties.Hidden_api.Split_packages - packagePrefixes := b.sourceOnlyProperties.Hidden_api.Package_prefixes - singlePackages := b.sourceOnlyProperties.Hidden_api.Single_packages + splitPackages := input.SplitPackages + packagePrefixes := input.PackagePrefixes + singlePackages := input.SinglePackages if splitPackages != nil || packagePrefixes != nil || singlePackages != nil { output.SignaturePatternsPath = buildRuleSignaturePatternsFile( ctx, output.AllFlagsPath, splitPackages, packagePrefixes, singlePackages) diff --git a/java/bootclasspath_fragment_test.go b/java/bootclasspath_fragment_test.go index 83beb6d23..f95c83fe7 100644 --- a/java/bootclasspath_fragment_test.go +++ b/java/bootclasspath_fragment_test.go @@ -15,6 +15,7 @@ package java import ( + "strings" "testing" "android/soong/android" @@ -285,6 +286,119 @@ func TestBootclasspathFragment_StubLibs(t *testing.T) { android.AssertPathsRelativeToTopEquals(t, "widest dex stubs jar", expectedWidestPaths, info.TransitiveStubDexJarsByScope.StubDexJarsForWidestAPIScope()) } +func TestSnapshotWithBootclasspathFragment_HiddenAPI(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForTestWithBootclasspathFragment, + PrepareForTestWithJavaSdkLibraryFiles, + FixtureWithLastReleaseApis("mysdklibrary", "mynewlibrary"), + FixtureConfigureApexBootJars("myapex:mybootlib", "myapex:mynewlibrary"), + android.MockFS{ + "my-blocked.txt": nil, + "my-max-target-o-low-priority.txt": nil, + "my-max-target-p.txt": nil, + "my-max-target-q.txt": nil, + "my-max-target-r-low-priority.txt": nil, + "my-removed.txt": nil, + "my-unsupported-packages.txt": nil, + "my-unsupported.txt": nil, + "my-new-max-target-q.txt": nil, + }.AddToFixture(), + android.FixtureWithRootAndroidBp(` + bootclasspath_fragment { + name: "mybootclasspathfragment", + apex_available: ["myapex"], + contents: ["mybootlib", "mynewlibrary"], + hidden_api: { + unsupported: [ + "my-unsupported.txt", + ], + removed: [ + "my-removed.txt", + ], + max_target_r_low_priority: [ + "my-max-target-r-low-priority.txt", + ], + max_target_q: [ + "my-max-target-q.txt", + ], + max_target_p: [ + "my-max-target-p.txt", + ], + max_target_o_low_priority: [ + "my-max-target-o-low-priority.txt", + ], + blocked: [ + "my-blocked.txt", + ], + unsupported_packages: [ + "my-unsupported-packages.txt", + ], + split_packages: ["sdklibrary"], + package_prefixes: ["sdklibrary.all.mine"], + single_packages: ["sdklibrary.mine"], + }, + } + + java_library { + name: "mybootlib", + apex_available: ["myapex"], + srcs: ["Test.java"], + system_modules: "none", + sdk_version: "none", + min_sdk_version: "1", + compile_dex: true, + permitted_packages: ["mybootlib"], + } + + java_sdk_library { + name: "mynewlibrary", + apex_available: ["myapex"], + srcs: ["Test.java"], + min_sdk_version: "10", + compile_dex: true, + public: {enabled: true}, + permitted_packages: ["mysdklibrary"], + hidden_api: { + max_target_q: [ + "my-new-max-target-q.txt", + ], + split_packages: ["sdklibrary", "newlibrary"], + package_prefixes: ["newlibrary.all.mine"], + single_packages: ["newlibrary.mine"], + }, + } + `), + ).RunTest(t) + + // Make sure that the library exports hidden API properties for use by the bootclasspath_fragment. + library := result.Module("mynewlibrary", "android_common") + info := result.ModuleProvider(library, hiddenAPIPropertyInfoProvider).(HiddenAPIPropertyInfo) + android.AssertArrayString(t, "split packages", []string{"sdklibrary", "newlibrary"}, info.SplitPackages) + android.AssertArrayString(t, "package prefixes", []string{"newlibrary.all.mine"}, info.PackagePrefixes) + android.AssertArrayString(t, "single packages", []string{"newlibrary.mine"}, info.SinglePackages) + for _, c := range HiddenAPIFlagFileCategories { + expectedMaxTargetQPaths := []string(nil) + if c.PropertyName == "max_target_q" { + expectedMaxTargetQPaths = []string{"my-new-max-target-q.txt"} + } + android.AssertPathsRelativeToTopEquals(t, c.PropertyName, expectedMaxTargetQPaths, info.FlagFilesByCategory[c]) + } + + // Make sure that the signature-patterns.csv is passed all the appropriate package properties + // from the bootclasspath_fragment and its contents. + fragment := result.ModuleForTests("mybootclasspathfragment", "android_common") + rule := fragment.Output("modular-hiddenapi/signature-patterns.csv") + expectedCommand := strings.Join([]string{ + "--split-package newlibrary", + "--split-package sdklibrary", + "--package-prefix newlibrary.all.mine", + "--package-prefix sdklibrary.all.mine", + "--single-package newlibrary.mine", + "--single-package sdklibrary", + }, " ") + android.AssertStringDoesContain(t, "signature patterns command", rule.RuleParams.Command, expectedCommand) +} + func TestBootclasspathFragment_Test(t *testing.T) { result := android.GroupFixturePreparers( prepareForTestWithBootclasspathFragment, diff --git a/java/classpath_element.go b/java/classpath_element.go index 753e7f888..496291678 100644 --- a/java/classpath_element.go +++ b/java/classpath_element.go @@ -97,11 +97,11 @@ type ClasspathElementContext interface { // 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. +// - 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 @@ -109,13 +109,15 @@ type ClasspathElementContext interface { // 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 +// +// 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), +// +// 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. diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go index b4cd07a78..4e416fc82 100644 --- a/java/dexpreopt_bootjars.go +++ b/java/dexpreopt_bootjars.go @@ -418,7 +418,6 @@ func (image *bootImageConfig) shouldInstallInApex() bool { // // The location is passed as an argument to the ART tools like dex2oat instead of the real path. // ART tools will then reconstruct the architecture-specific real path. -// func (image *bootImageVariant) imageLocations() (imageLocationsOnHost []string, imageLocationsOnDevice []string) { if image.extends != nil { imageLocationsOnHost, imageLocationsOnDevice = image.extends.getVariant(image.target).imageLocations() diff --git a/java/droiddoc.go b/java/droiddoc.go index 96639220a..901419cba 100644 --- a/java/droiddoc.go +++ b/java/droiddoc.go @@ -158,9 +158,7 @@ type DroiddocProperties struct { Compat_config *string `android:"path"` } -// // Common flags passed down to build rule -// type droiddocBuilderFlags struct { bootClasspathArgs string classpathArgs string @@ -193,9 +191,7 @@ func apiCheckEnabled(ctx android.ModuleContext, apiToCheck ApiToCheck, apiVersio return false } -// // Javadoc -// type Javadoc struct { android.ModuleBase android.DefaultableModuleBase @@ -548,9 +544,7 @@ func (j *Javadoc) GenerateAndroidBuildActions(ctx android.ModuleContext) { rule.Build("javadoc", "javadoc") } -// // Droiddoc -// type Droiddoc struct { Javadoc @@ -827,9 +821,7 @@ func (d *Droiddoc) GenerateAndroidBuildActions(ctx android.ModuleContext) { rule.Build("javadoc", desc) } -// // Exported Droiddoc Directory -// var droiddocTemplateTag = dependencyTag{name: "droiddoc-template"} type ExportedDroiddocDirProperties struct { @@ -862,9 +854,7 @@ func (d *ExportedDroiddocDir) GenerateAndroidBuildActions(ctx android.ModuleCont d.deps = android.PathsForModuleSrc(ctx, []string{filepath.Join(path, "**/*")}) } -// // Defaults -// type DocDefaults struct { android.ModuleBase android.DefaultsModuleBase diff --git a/java/droidstubs.go b/java/droidstubs.go index 932fb19ce..12590ca50 100644 --- a/java/droidstubs.go +++ b/java/droidstubs.go @@ -42,19 +42,14 @@ func RegisterStubsBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("prebuilt_stubs_sources", PrebuiltStubsSourcesFactory) } -// // Droidstubs -// type Droidstubs struct { Javadoc android.SdkBase properties DroidstubsProperties - apiFile android.WritablePath - apiXmlFile android.WritablePath - lastReleasedApiXmlFile android.WritablePath - privateApiFile android.WritablePath - removedApiFile android.WritablePath + apiFile android.Path + removedApiFile android.Path nullabilityWarningsFile android.WritablePath checkCurrentApiTimestamp android.WritablePath @@ -68,9 +63,6 @@ type Droidstubs struct { annotationsZip android.WritablePath apiVersionsXml android.WritablePath - apiFilePath android.Path - removedApiFilePath android.Path - metadataZip android.WritablePath metadataDir android.WritablePath } @@ -206,9 +198,9 @@ func (d *Droidstubs) OutputFiles(tag string) (android.Paths, error) { return android.Paths{d.docZip}, nil case ".api.txt", android.DefaultDistTag: // This is the default dist path for dist properties that have no tag property. - return android.Paths{d.apiFilePath}, nil + return android.Paths{d.apiFile}, nil case ".removed-api.txt": - return android.Paths{d.removedApiFilePath}, nil + return android.Paths{d.removedApiFile}, nil case ".annotations.zip": return android.Paths{d.annotationsZip}, nil case ".api_versions.xml": @@ -223,11 +215,11 @@ func (d *Droidstubs) AnnotationsZip() android.Path { } func (d *Droidstubs) ApiFilePath() android.Path { - return d.apiFilePath + return d.apiFile } func (d *Droidstubs) RemovedApiFilePath() android.Path { - return d.removedApiFilePath + return d.removedApiFile } func (d *Droidstubs) StubsSrcJar() android.Path { @@ -270,24 +262,24 @@ func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuil apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") || String(d.properties.Api_filename) != "" { filename := proptools.StringDefault(d.properties.Api_filename, ctx.ModuleName()+"_api.txt") - d.apiFile = android.PathForModuleOut(ctx, "metalava", filename) - cmd.FlagWithOutput("--api ", d.apiFile) - d.apiFilePath = d.apiFile + uncheckedApiFile := android.PathForModuleOut(ctx, "metalava", filename) + cmd.FlagWithOutput("--api ", uncheckedApiFile) + d.apiFile = uncheckedApiFile } else if sourceApiFile := proptools.String(d.properties.Check_api.Current.Api_file); sourceApiFile != "" { // If check api is disabled then make the source file available for export. - d.apiFilePath = android.PathForModuleSrc(ctx, sourceApiFile) + d.apiFile = android.PathForModuleSrc(ctx, sourceApiFile) } if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") || apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") || String(d.properties.Removed_api_filename) != "" { filename := proptools.StringDefault(d.properties.Removed_api_filename, ctx.ModuleName()+"_removed.txt") - d.removedApiFile = android.PathForModuleOut(ctx, "metalava", filename) - cmd.FlagWithOutput("--removed-api ", d.removedApiFile) - d.removedApiFilePath = d.removedApiFile + uncheckedRemovedFile := android.PathForModuleOut(ctx, "metalava", filename) + cmd.FlagWithOutput("--removed-api ", uncheckedRemovedFile) + d.removedApiFile = uncheckedRemovedFile } else if sourceRemovedApiFile := proptools.String(d.properties.Check_api.Current.Removed_api_file); sourceRemovedApiFile != "" { // If check api is disabled then make the source removed api file available for export. - d.removedApiFilePath = android.PathForModuleSrc(ctx, sourceRemovedApiFile) + d.removedApiFile = android.PathForModuleSrc(ctx, sourceRemovedApiFile) } if Bool(d.properties.Write_sdk_values) { @@ -697,15 +689,86 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { zipSyncCleanupCmd(rule, srcJarDir) + rule.Build("metalava", "metalava merged") + if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") { - d.generateCheckCurrentCheckedInApiIsUpToDateBuildRules(ctx) - // Make sure that whenever the API stubs are generated that the current checked in API files are - // checked to make sure that they are up-to-date. - cmd.Validation(d.checkCurrentApiTimestamp) - } + if len(d.Javadoc.properties.Out) > 0 { + ctx.PropertyErrorf("out", "out property may not be combined with check_api") + } - rule.Build("metalava", "metalava merged") + apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Api_file)) + removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Removed_api_file)) + baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Current.Baseline_file) + + if baselineFile.Valid() { + ctx.PropertyErrorf("baseline_file", "current API check can't have a baseline file. (module %s)", ctx.ModuleName()) + } + + d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "check_current_api.timestamp") + + rule := android.NewRuleBuilder(pctx, ctx) + + // Diff command line. + // -F matches the closest "opening" line, such as "package android {" + // and " public class Intent {". + diff := `diff -u -F '{ *$'` + + rule.Command().Text("( true") + rule.Command(). + Text(diff). + Input(apiFile).Input(d.apiFile) + + rule.Command(). + Text(diff). + Input(removedApiFile).Input(d.removedApiFile) + + msg := fmt.Sprintf(`\n******************************\n`+ + `You have tried to change the API from what has been previously approved.\n\n`+ + `To make these errors go away, you have two choices:\n`+ + ` 1. You can add '@hide' javadoc comments (and remove @SystemApi/@TestApi/etc)\n`+ + ` to the new methods, etc. shown in the above diff.\n\n`+ + ` 2. You can update current.txt and/or removed.txt by executing the following command:\n`+ + ` m %s-update-current-api\n\n`+ + ` To submit the revised current.txt to the main Android repository,\n`+ + ` you will need approval.\n`+ + `******************************\n`, ctx.ModuleName()) + + rule.Command(). + Text("touch").Output(d.checkCurrentApiTimestamp). + Text(") || ("). + Text("echo").Flag("-e").Flag(`"` + msg + `"`). + Text("; exit 38"). + Text(")") + + rule.Build("metalavaCurrentApiCheck", "check current API") + + d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "update_current_api.timestamp") + + // update API rule + rule = android.NewRuleBuilder(pctx, ctx) + + rule.Command().Text("( true") + + rule.Command(). + Text("cp").Flag("-f"). + Input(d.apiFile).Flag(apiFile.String()) + + rule.Command(). + Text("cp").Flag("-f"). + Input(d.removedApiFile).Flag(removedApiFile.String()) + + msg = "failed to update public API" + + rule.Command(). + Text("touch").Output(d.updateCurrentApiTimestamp). + Text(") || ("). + Text("echo").Flag("-e").Flag(`"` + msg + `"`). + Text("; exit 38"). + Text(")") + + rule.Build("metalavaCurrentApiUpdate", "update current API") + } if String(d.properties.Check_nullability_warnings) != "" { if d.nullabilityWarningsFile == nil { @@ -743,84 +806,6 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { } } -func (d *Droidstubs) generateCheckCurrentCheckedInApiIsUpToDateBuildRules(ctx android.ModuleContext) { - if len(d.Javadoc.properties.Out) > 0 { - ctx.PropertyErrorf("out", "out property may not be combined with check_api") - } - - apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Api_file)) - removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Removed_api_file)) - baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Current.Baseline_file) - - if baselineFile.Valid() { - ctx.PropertyErrorf("baseline_file", "current API check can't have a baseline file. (module %s)", ctx.ModuleName()) - } - - d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "check_current_api.timestamp") - - rule := android.NewRuleBuilder(pctx, ctx) - - // Diff command line. - // -F matches the closest "opening" line, such as "package android {" - // and " public class Intent {". - diff := `diff -u -F '{ *$'` - - rule.Command().Text("( true") - rule.Command(). - Text(diff). - Input(apiFile).Input(d.apiFile) - - rule.Command(). - Text(diff). - Input(removedApiFile).Input(d.removedApiFile) - - msg := fmt.Sprintf(`\n******************************\n`+ - `You have tried to change the API from what has been previously approved.\n\n`+ - `To make these errors go away, you have two choices:\n`+ - ` 1. You can add '@hide' javadoc comments (and remove @SystemApi/@TestApi/etc)\n`+ - ` to the new methods, etc. shown in the above diff.\n\n`+ - ` 2. You can update current.txt and/or removed.txt by executing the following command:\n`+ - ` m %s-update-current-api\n\n`+ - ` To submit the revised current.txt to the main Android repository,\n`+ - ` you will need approval.\n`+ - `******************************\n`, ctx.ModuleName()) - - rule.Command(). - Text("touch").Output(d.checkCurrentApiTimestamp). - Text(") || ("). - Text("echo").Flag("-e").Flag(`"` + msg + `"`). - Text("; exit 38"). - Text(")") - - rule.Build("metalavaCurrentApiCheck", "check current API") - - d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "update_current_api.timestamp") - - // update API rule - rule = android.NewRuleBuilder(pctx, ctx) - - rule.Command().Text("( true") - - rule.Command(). - Text("cp").Flag("-f"). - Input(d.apiFile).Flag(apiFile.String()) - - rule.Command(). - Text("cp").Flag("-f"). - Input(d.removedApiFile).Flag(removedApiFile.String()) - - msg = "failed to update public API" - - rule.Command(). - Text("touch").Output(d.updateCurrentApiTimestamp). - Text(") || ("). - Text("echo").Flag("-e").Flag(`"` + msg + `"`). - Text("; exit 38"). - Text(")") - - rule.Build("metalavaCurrentApiUpdate", "update current API") -} - func StubsDefaultsFactory() android.Module { module := &DocDefaults{} diff --git a/java/genrule.go b/java/genrule.go index 5047c412f..208e1f43b 100644 --- a/java/genrule.go +++ b/java/genrule.go @@ -43,23 +43,23 @@ func RegisterGenRuleBuildComponents(ctx android.RegistrationContext) { // // Use a java_genrule to package generated java resources: // -// java_genrule { -// name: "generated_resources", -// tools: [ -// "generator", -// "soong_zip", -// ], -// srcs: ["generator_inputs/**/*"], -// out: ["generated_android_icu4j_resources.jar"], -// cmd: "$(location generator) $(in) -o $(genDir) " + -// "&& $(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res", -// } +// java_genrule { +// name: "generated_resources", +// tools: [ +// "generator", +// "soong_zip", +// ], +// srcs: ["generator_inputs/**/*"], +// out: ["generated_android_icu4j_resources.jar"], +// cmd: "$(location generator) $(in) -o $(genDir) " + +// "&& $(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res", +// } // -// java_library { -// name: "lib_with_generated_resources", -// srcs: ["src/**/*.java"], -// static_libs: ["generated_resources"], -// } +// java_library { +// name: "lib_with_generated_resources", +// srcs: ["src/**/*.java"], +// static_libs: ["generated_resources"], +// } func GenRuleFactory() android.Module { module := genrule.NewGenRule() diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go index c90b2ff97..7b678037c 100644 --- a/java/hiddenapi_modular.go +++ b/java/hiddenapi_modular.go @@ -378,32 +378,37 @@ func buildRuleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, name, // with one Java package per line. All members of all classes within that package (but not nested // packages) will be updated in a property specific way. type HiddenAPIFlagFileProperties struct { - // Marks each signature in the referenced files as being unsupported. - Unsupported []string `android:"path"` + Hidden_api struct { + // Marks each signature in the referenced files as being unsupported. + Unsupported []string `android:"path"` - // Marks each signature in the referenced files as being unsupported because it has been removed. - // Any conflicts with other flags are ignored. - Removed []string `android:"path"` + // Marks each signature in the referenced files as being unsupported because it has been + // removed. Any conflicts with other flags are ignored. + Removed []string `android:"path"` - // Marks each signature in the referenced files as being supported only for targetSdkVersion <= R - // and low priority. - Max_target_r_low_priority []string `android:"path"` + // Marks each signature in the referenced files as being supported only for + // targetSdkVersion <= R and low priority. + Max_target_r_low_priority []string `android:"path"` - // Marks each signature in the referenced files as being supported only for targetSdkVersion <= Q. - Max_target_q []string `android:"path"` + // Marks each signature in the referenced files as being supported only for + // targetSdkVersion <= Q. + Max_target_q []string `android:"path"` - // Marks each signature in the referenced files as being supported only for targetSdkVersion <= P. - Max_target_p []string `android:"path"` + // Marks each signature in the referenced files as being supported only for + // targetSdkVersion <= P. + Max_target_p []string `android:"path"` - // Marks each signature in the referenced files as being supported only for targetSdkVersion <= O - // and low priority. Any conflicts with other flags are ignored. - Max_target_o_low_priority []string `android:"path"` + // Marks each signature in the referenced files as being supported only for + // targetSdkVersion <= O + // and low priority. Any conflicts with other flags are ignored. + Max_target_o_low_priority []string `android:"path"` - // Marks each signature in the referenced files as being blocked. - Blocked []string `android:"path"` + // Marks each signature in the referenced files as being blocked. + Blocked []string `android:"path"` - // Marks each signature in every package in the referenced files as being unsupported. - Unsupported_packages []string `android:"path"` + // Marks each signature in every package in the referenced files as being unsupported. + Unsupported_packages []string `android:"path"` + } } type hiddenAPIFlagFileCategory struct { @@ -428,19 +433,30 @@ var hiddenAPIRemovedFlagFileCategory = &hiddenAPIFlagFileCategory{ // See HiddenAPIFlagFileProperties.Removed PropertyName: "removed", propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string { - return properties.Removed + return properties.Hidden_api.Removed }, commandMutator: func(command *android.RuleBuilderCommand, path android.Path) { command.FlagWithInput("--unsupported ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed") }, } -var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{ +type hiddenAPIFlagFileCategories []*hiddenAPIFlagFileCategory + +func (c hiddenAPIFlagFileCategories) byProperty(name string) *hiddenAPIFlagFileCategory { + for _, category := range c { + if category.PropertyName == name { + return category + } + } + panic(fmt.Errorf("no category exists with property name %q in %v", name, c)) +} + +var HiddenAPIFlagFileCategories = hiddenAPIFlagFileCategories{ // See HiddenAPIFlagFileProperties.Unsupported { PropertyName: "unsupported", propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string { - return properties.Unsupported + return properties.Hidden_api.Unsupported }, commandMutator: func(command *android.RuleBuilderCommand, path android.Path) { command.FlagWithInput("--unsupported ", path) @@ -451,7 +467,7 @@ var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{ { PropertyName: "max_target_r_low_priority", propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string { - return properties.Max_target_r_low_priority + return properties.Hidden_api.Max_target_r_low_priority }, commandMutator: func(command *android.RuleBuilderCommand, path android.Path) { command.FlagWithInput("--max-target-r ", path).FlagWithArg("--tag ", "lo-prio") @@ -461,7 +477,7 @@ var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{ { PropertyName: "max_target_q", propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string { - return properties.Max_target_q + return properties.Hidden_api.Max_target_q }, commandMutator: func(command *android.RuleBuilderCommand, path android.Path) { command.FlagWithInput("--max-target-q ", path) @@ -471,7 +487,7 @@ var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{ { PropertyName: "max_target_p", propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string { - return properties.Max_target_p + return properties.Hidden_api.Max_target_p }, commandMutator: func(command *android.RuleBuilderCommand, path android.Path) { command.FlagWithInput("--max-target-p ", path) @@ -481,7 +497,7 @@ var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{ { PropertyName: "max_target_o_low_priority", propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string { - return properties.Max_target_o_low_priority + return properties.Hidden_api.Max_target_o_low_priority }, commandMutator: func(command *android.RuleBuilderCommand, path android.Path) { command.FlagWithInput("--max-target-o ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio") @@ -491,7 +507,7 @@ var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{ { PropertyName: "blocked", propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string { - return properties.Blocked + return properties.Hidden_api.Blocked }, commandMutator: func(command *android.RuleBuilderCommand, path android.Path) { command.FlagWithInput("--blocked ", path) @@ -501,7 +517,7 @@ var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{ { PropertyName: "unsupported_packages", propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string { - return properties.Unsupported_packages + return properties.Hidden_api.Unsupported_packages }, commandMutator: func(command *android.RuleBuilderCommand, path android.Path) { command.FlagWithInput("--unsupported ", path).Flag("--packages ") @@ -512,13 +528,20 @@ var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{ // FlagFilesByCategory maps a hiddenAPIFlagFileCategory to the paths to the files in that category. type FlagFilesByCategory map[*hiddenAPIFlagFileCategory]android.Paths -// append appends the supplied flags files to the corresponding category in this map. +// append the supplied flags files to the corresponding category in this map. func (s FlagFilesByCategory) append(other FlagFilesByCategory) { for _, category := range HiddenAPIFlagFileCategories { s[category] = append(s[category], other[category]...) } } +// sort the paths for each category in this map. +func (s FlagFilesByCategory) sort() { + for category, value := range s { + s[category] = android.SortedUniquePaths(value) + } +} + // HiddenAPIInfo contains information provided by the hidden API processing. // // That includes paths resolved from HiddenAPIFlagFileProperties and also generated by hidden API @@ -686,13 +709,70 @@ func (s StubDexJarsByModule) StubDexJarsForScope(scope *HiddenAPIScope) android. return stubDexJars } -// HiddenAPIFlagInput encapsulates information obtained from a module and its dependencies that are -// needed for hidden API flag generation. -type HiddenAPIFlagInput struct { +type HiddenAPIPropertyInfo struct { // FlagFilesByCategory contains the flag files that override the initial flags that are derived // from the stub dex files. FlagFilesByCategory FlagFilesByCategory + // See HiddenAPIFlagFileProperties.Package_prefixes + PackagePrefixes []string + + // See HiddenAPIFlagFileProperties.Single_packages + SinglePackages []string + + // See HiddenAPIFlagFileProperties.Split_packages + SplitPackages []string +} + +var hiddenAPIPropertyInfoProvider = blueprint.NewProvider(HiddenAPIPropertyInfo{}) + +// newHiddenAPIPropertyInfo creates a new initialized HiddenAPIPropertyInfo struct. +func newHiddenAPIPropertyInfo() HiddenAPIPropertyInfo { + return HiddenAPIPropertyInfo{ + FlagFilesByCategory: FlagFilesByCategory{}, + } +} + +// extractFlagFilesFromProperties extracts the paths to flag files that are specified in the +// supplied properties and stores them in this struct. +func (i *HiddenAPIPropertyInfo) extractFlagFilesFromProperties(ctx android.ModuleContext, p *HiddenAPIFlagFileProperties) { + for _, category := range HiddenAPIFlagFileCategories { + paths := android.PathsForModuleSrc(ctx, category.propertyValueReader(p)) + i.FlagFilesByCategory[category] = paths + } +} + +// extractPackageRulesFromProperties extracts the package rules that are specified in the supplied +// properties and stores them in this struct. +func (i *HiddenAPIPropertyInfo) extractPackageRulesFromProperties(p *HiddenAPIPackageProperties) { + i.PackagePrefixes = p.Hidden_api.Package_prefixes + i.SinglePackages = p.Hidden_api.Single_packages + i.SplitPackages = p.Hidden_api.Split_packages +} + +func (i *HiddenAPIPropertyInfo) gatherPropertyInfo(ctx android.ModuleContext, contents []android.Module) { + for _, module := range contents { + if ctx.OtherModuleHasProvider(module, hiddenAPIPropertyInfoProvider) { + info := ctx.OtherModuleProvider(module, hiddenAPIPropertyInfoProvider).(HiddenAPIPropertyInfo) + i.FlagFilesByCategory.append(info.FlagFilesByCategory) + i.PackagePrefixes = append(i.PackagePrefixes, info.PackagePrefixes...) + i.SinglePackages = append(i.SinglePackages, info.SinglePackages...) + i.SplitPackages = append(i.SplitPackages, info.SplitPackages...) + } + } + + // Dedup and sort the information to ensure consistent builds. + i.FlagFilesByCategory.sort() + i.PackagePrefixes = android.SortedUniqueStrings(i.PackagePrefixes) + i.SinglePackages = android.SortedUniqueStrings(i.SinglePackages) + i.SplitPackages = android.SortedUniqueStrings(i.SplitPackages) +} + +// HiddenAPIFlagInput encapsulates information obtained from a module and its dependencies that are +// needed for hidden API flag generation. +type HiddenAPIFlagInput struct { + HiddenAPIPropertyInfo + // StubDexJarsByScope contains the stub dex jars for different *HiddenAPIScope and which determine // the initial flags for each dex member. StubDexJarsByScope StubDexJarsByModule @@ -714,10 +794,10 @@ type HiddenAPIFlagInput struct { RemovedTxtFiles android.Paths } -// newHiddenAPIFlagInput creates a new initialize HiddenAPIFlagInput struct. +// newHiddenAPIFlagInput creates a new initialized HiddenAPIFlagInput struct. func newHiddenAPIFlagInput() HiddenAPIFlagInput { input := HiddenAPIFlagInput{ - FlagFilesByCategory: FlagFilesByCategory{}, + HiddenAPIPropertyInfo: newHiddenAPIPropertyInfo(), StubDexJarsByScope: StubDexJarsByModule{}, DependencyStubDexJarsByScope: StubDexJarsByModule{}, AdditionalStubDexJarsByScope: StubDexJarsByModule{}, @@ -773,15 +853,6 @@ func (i *HiddenAPIFlagInput) gatherStubLibInfo(ctx android.ModuleContext, conten i.RemovedTxtFiles = android.SortedUniquePaths(i.RemovedTxtFiles) } -// extractFlagFilesFromProperties extracts the paths to flag files that are specified in the -// supplied properties and stores them in this struct. -func (i *HiddenAPIFlagInput) extractFlagFilesFromProperties(ctx android.ModuleContext, p *HiddenAPIFlagFileProperties) { - for _, category := range HiddenAPIFlagFileCategories { - paths := android.PathsForModuleSrc(ctx, category.propertyValueReader(p)) - i.FlagFilesByCategory[category] = paths - } -} - func (i *HiddenAPIFlagInput) transitiveStubDexJarsByScope() StubDexJarsByModule { transitive := i.DependencyStubDexJarsByScope transitive.addStubDexJarsByModule(i.StubDexJarsByScope) diff --git a/java/java.go b/java/java.go index 77ab40279..210b883e1 100644 --- a/java/java.go +++ b/java/java.go @@ -629,6 +629,9 @@ func setUncompressDex(ctx android.ModuleContext, dexpreopter *dexpreopter, dexer } func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) { + + j.provideHiddenAPIPropertyInfo(ctx) + j.sdkVersion = j.SdkVersion(ctx) j.minSdkVersion = j.MinSdkVersion(ctx) j.maxSdkVersion = j.MaxSdkVersion(ctx) @@ -852,11 +855,10 @@ func LibraryHostFactory() android.Module { // Test option struct. type TestOptions struct { + android.CommonTestOptions + // a list of extra test configuration files that should be installed with the module. Extra_test_configs []string `android:"path,arch_variant"` - - // If the test is a hostside(no device required) unittest that shall be run during presubmit check. - Unit_test *bool } type testProperties struct { diff --git a/java/java_test.go b/java/java_test.go index 9e5cf0cf2..bfd97eb0d 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -1287,6 +1287,41 @@ func TestAidlExportIncludeDirsFromImports(t *testing.T) { } } +func TestAidlIncludeDirFromConvertedFileGroupWithPathPropInMixedBuilds(t *testing.T) { + bp := ` + filegroup { + name: "foo_aidl", + srcs: ["aidl/foo/IFoo.aidl"], + path: "aidl/foo", + bazel_module: { label: "//:foo_aidl" }, + } + java_library { + name: "foo", + srcs: [":foo_aidl"], + } +` + + outBaseDir := "out/bazel/output" + result := android.GroupFixturePreparers( + prepareForJavaTest, + android.PrepareForTestWithFilegroup, + android.FixtureModifyConfig(func(config android.Config) { + config.BazelContext = android.MockBazelContext{ + OutputBaseDir: outBaseDir, + LabelToOutputFiles: map[string][]string{ + "//:foo_aidl": []string{"aidl/foo/IFoo.aidl"}, + }, + } + }), + ).RunTestWithBp(t, bp) + + aidlCommand := result.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command + expectedAidlFlag := "-I" + outBaseDir + "/execroot/__main__/aidl/foo" + if !strings.Contains(aidlCommand, expectedAidlFlag) { + t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag) + } +} + func TestAidlFlagsArePassedToTheAidlCompiler(t *testing.T) { ctx, _ := testJava(t, ` java_library { diff --git a/java/lint.go b/java/lint.go index 67746204e..931820d74 100644 --- a/java/lint.go +++ b/java/lint.go @@ -331,12 +331,18 @@ func (l *linter) lint(ctx android.ModuleContext) { if l.minSdkVersion != l.compileSdkVersion { l.extraMainlineLintErrors = append(l.extraMainlineLintErrors, updatabilityChecks...) - _, filtered := android.FilterList(l.properties.Lint.Warning_checks, updatabilityChecks) - if len(filtered) != 0 { - ctx.PropertyErrorf("lint.warning_checks", - "Can't treat %v checks as warnings if min_sdk_version is different from sdk_version.", filtered) + // Skip lint warning checks for NewApi warnings for libcore where they come from source + // files that reference the API they are adding (b/208656169). + if ctx.ModuleDir() != "libcore" { + _, filtered := android.FilterList(l.properties.Lint.Warning_checks, updatabilityChecks) + + if len(filtered) != 0 { + ctx.PropertyErrorf("lint.warning_checks", + "Can't treat %v checks as warnings if min_sdk_version is different from sdk_version.", filtered) + } } - _, filtered = android.FilterList(l.properties.Lint.Disabled_checks, updatabilityChecks) + + _, filtered := android.FilterList(l.properties.Lint.Disabled_checks, updatabilityChecks) if len(filtered) != 0 { ctx.PropertyErrorf("lint.disabled_checks", "Can't disable %v checks if min_sdk_version is different from sdk_version.", filtered) diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go index 1e2723845..24f8253ae 100644 --- a/java/platform_bootclasspath.go +++ b/java/platform_bootclasspath.go @@ -62,7 +62,7 @@ type platformBootclasspathModule struct { type platformBootclasspathProperties struct { BootclasspathFragmentsDepsProperties - Hidden_api HiddenAPIFlagFileProperties + HiddenAPIFlagFileProperties } func platformBootclasspathFactory() android.SingletonModule { @@ -372,7 +372,7 @@ func (b *platformBootclasspathModule) createAndProvideMonolithicHiddenAPIInfo(ct temporaryInput := newHiddenAPIFlagInput() // Create paths to the flag files specified in the properties. - temporaryInput.extractFlagFilesFromProperties(ctx, &b.properties.Hidden_api) + temporaryInput.extractFlagFilesFromProperties(ctx, &b.properties.HiddenAPIFlagFileProperties) // Create the monolithic info, by starting with the flag files specified on this and then merging // in information from all the fragment dependencies of this. diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go index 1c4249507..655021fc4 100644 --- a/java/platform_compat_config.go +++ b/java/platform_compat_config.go @@ -280,7 +280,7 @@ func platformCompatConfigSingletonFactory() android.Singleton { return &platformCompatConfigSingleton{} } -//============== merged_compat_config ================= +// ============== merged_compat_config ================= type globalCompatConfigProperties struct { // name of the file into which the metadata will be copied. Filename *string diff --git a/java/sdk_library.go b/java/sdk_library.go index 490c03132..8f499b101 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -2129,11 +2129,12 @@ var _ SdkLibraryDependency = (*SdkLibraryImport)(nil) // The type of a structure that contains a field of type sdkLibraryScopeProperties // for each apiscope in allApiScopes, e.g. something like: -// struct { -// Public sdkLibraryScopeProperties -// System sdkLibraryScopeProperties -// ... -// } +// +// struct { +// Public sdkLibraryScopeProperties +// System sdkLibraryScopeProperties +// ... +// } var allScopeStructType = createAllScopePropertiesStructType() // Dynamically create a structure type for each apiscope in allApiScopes. @@ -2556,9 +2557,7 @@ func (module *SdkLibraryImport) RequiredFilesFromPrebuiltApex(ctx android.BaseMo return requiredFilesFromPrebuiltApexForImport(name) } -// // java_sdk_library_xml -// type sdkLibraryXml struct { android.ModuleBase android.DefaultableModuleBase diff --git a/java/sdk_library_external.go b/java/sdk_library_external.go index 0acaa13b2..4f8398194 100644 --- a/java/sdk_library_external.go +++ b/java/sdk_library_external.go @@ -49,9 +49,10 @@ func (g partitionGroup) String() string { // Get partition group of java module that can be used at inter-partition dependency check. // We currently have three groups -// (system, system_ext) => system partition group -// (vendor, odm) => vendor partition group -// (product) => product partition group +// +// (system, system_ext) => system partition group +// (vendor, odm) => vendor partition group +// (product) => product partition group func (j *Module) partitionGroup(ctx android.EarlyModuleContext) partitionGroup { // system and system_ext partition can be treated as the same in terms of inter-partition dependency. if j.Platform() || j.SystemExtSpecific() { |