1// Copyright 2018 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package tradefed 16 17import ( 18 "fmt" 19 "strings" 20 21 "github.com/google/blueprint" 22 "github.com/google/blueprint/proptools" 23 24 "android/soong/android" 25) 26 27const test_xml_indent = " " 28 29func getTestConfigTemplate(ctx android.ModuleContext, prop *string) android.OptionalPath { 30 return ctx.ExpandOptionalSource(prop, "test_config_template") 31} 32 33func getTestConfig(ctx android.ModuleContext, prop *string) android.Path { 34 if p := ctx.ExpandOptionalSource(prop, "test_config"); p.Valid() { 35 return p.Path() 36 } else if p := android.ExistentPathForSource(ctx, ctx.ModuleDir(), "AndroidTest.xml"); p.Valid() { 37 return p.Path() 38 } 39 return nil 40} 41 42var autogenTestConfig = pctx.StaticRule("autogenTestConfig", blueprint.RuleParams{ 43 Command: "sed 's&{MODULE}&${name}&g;s&{EXTRA_CONFIGS}&'${extraConfigs}'&g;s&{OUTPUT_FILENAME}&'${outputFileName}'&g;s&{TEST_INSTALL_BASE}&'${testInstallBase}'&g' $template > $out", 44 CommandDeps: []string{"$template"}, 45}, "name", "template", "extraConfigs", "outputFileName", "testInstallBase") 46 47func testConfigPath(ctx android.ModuleContext, prop *string, testSuites []string, autoGenConfig *bool, testConfigTemplateProp *string) (path android.Path, autogenPath android.WritablePath) { 48 p := getTestConfig(ctx, prop) 49 if !Bool(autoGenConfig) && p != nil { 50 return p, nil 51 } else if BoolDefault(autoGenConfig, true) && (!android.InList("cts", testSuites) || testConfigTemplateProp != nil) { 52 outputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".config") 53 return nil, outputFile 54 } else { 55 // CTS modules can be used for test data, so test config files must be 56 // explicitly created using AndroidTest.xml or test_config_template. 57 return nil, nil 58 } 59} 60 61type Config interface { 62 Config() string 63} 64 65type Option struct { 66 Name string 67 Key string 68 Value string 69} 70 71var _ Config = Option{} 72 73func (o Option) Config() string { 74 if o.Key != "" { 75 return fmt.Sprintf(`<option name="%s" key="%s" value="%s" />`, o.Name, o.Key, o.Value) 76 } 77 return fmt.Sprintf(`<option name="%s" value="%s" />`, o.Name, o.Value) 78} 79 80// It can be a template of object or target_preparer. 81type Object struct { 82 // Set it as a target_preparer if object type == "target_preparer". 83 Type string 84 Class string 85 Options []Option 86} 87 88var _ Config = Object{} 89 90func (ob Object) Config() string { 91 var optionStrings []string 92 for _, option := range ob.Options { 93 optionStrings = append(optionStrings, option.Config()) 94 } 95 var options string 96 if len(ob.Options) == 0 { 97 options = "" 98 } else { 99 optionDelimiter := fmt.Sprintf("\\n%s%s", test_xml_indent, test_xml_indent) 100 options = optionDelimiter + strings.Join(optionStrings, optionDelimiter) 101 } 102 if ob.Type == "target_preparer" { 103 return fmt.Sprintf(`<target_preparer class="%s">%s\n%s</target_preparer>`, ob.Class, options, test_xml_indent) 104 } else { 105 return fmt.Sprintf(`<object type="%s" class="%s">%s\n%s</object>`, ob.Type, ob.Class, options, test_xml_indent) 106 } 107 108} 109 110func autogenTemplate(ctx android.ModuleContext, name string, output android.WritablePath, template string, configs []Config, outputFileName string, testInstallBase string) { 111 if template == "" { 112 ctx.ModuleErrorf("Empty template") 113 } 114 var configStrings []string 115 for _, config := range configs { 116 configStrings = append(configStrings, config.Config()) 117 } 118 extraConfigs := strings.Join(configStrings, fmt.Sprintf("\\n%s", test_xml_indent)) 119 extraConfigs = proptools.NinjaAndShellEscape(extraConfigs) 120 121 ctx.Build(pctx, android.BuildParams{ 122 Rule: autogenTestConfig, 123 Description: "test config", 124 Output: output, 125 Args: map[string]string{ 126 "name": name, 127 "template": template, 128 "extraConfigs": extraConfigs, 129 "outputFileName": outputFileName, 130 "testInstallBase": testInstallBase, 131 }, 132 }) 133} 134 135// AutoGenTestConfigOptions is used so that we can supply many optional 136// arguments to the AutoGenTestConfig function. 137type AutoGenTestConfigOptions struct { 138 Name string 139 OutputFileName string 140 TestConfigProp *string 141 TestConfigTemplateProp *string 142 TestSuites []string 143 Config []Config 144 OptionsForAutogenerated []Option 145 AutoGenConfig *bool 146 UnitTest *bool 147 TestInstallBase string 148 DeviceTemplate string 149 HostTemplate string 150 HostUnitTestTemplate string 151} 152 153func AutoGenTestConfig(ctx android.ModuleContext, options AutoGenTestConfigOptions) android.Path { 154 configs := append([]Config{}, options.Config...) 155 for _, c := range options.OptionsForAutogenerated { 156 configs = append(configs, c) 157 } 158 name := options.Name 159 if name == "" { 160 name = ctx.ModuleName() 161 } 162 path, autogenPath := testConfigPath(ctx, options.TestConfigProp, options.TestSuites, options.AutoGenConfig, options.TestConfigTemplateProp) 163 if autogenPath != nil { 164 templatePath := getTestConfigTemplate(ctx, options.TestConfigTemplateProp) 165 if templatePath.Valid() { 166 autogenTemplate(ctx, name, autogenPath, templatePath.String(), configs, options.OutputFileName, options.TestInstallBase) 167 } else { 168 if ctx.Device() { 169 autogenTemplate(ctx, name, autogenPath, options.DeviceTemplate, configs, options.OutputFileName, options.TestInstallBase) 170 } else { 171 if Bool(options.UnitTest) { 172 autogenTemplate(ctx, name, autogenPath, options.HostUnitTestTemplate, configs, options.OutputFileName, options.TestInstallBase) 173 } else { 174 autogenTemplate(ctx, name, autogenPath, options.HostTemplate, configs, options.OutputFileName, options.TestInstallBase) 175 } 176 } 177 } 178 return autogenPath 179 } 180 if len(options.OptionsForAutogenerated) > 0 { 181 ctx.ModuleErrorf("Extra tradefed configurations were provided for an autogenerated xml file, but the autogenerated xml file was not used.") 182 } 183 return path 184} 185 186var autogenInstrumentationTest = pctx.StaticRule("autogenInstrumentationTest", blueprint.RuleParams{ 187 Command: "${AutoGenTestConfigScript} $out $in ${EmptyTestConfig} $template ${extraConfigs}", 188 CommandDeps: []string{ 189 "${AutoGenTestConfigScript}", 190 "${EmptyTestConfig}", 191 "$template", 192 }, 193}, "name", "template", "extraConfigs") 194 195func AutoGenInstrumentationTestConfig(ctx android.ModuleContext, testConfigProp *string, 196 testConfigTemplateProp *string, manifest android.Path, testSuites []string, autoGenConfig *bool, configs []Config) android.Path { 197 path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp) 198 var configStrings []string 199 if autogenPath != nil { 200 template := "${InstrumentationTestConfigTemplate}" 201 moduleTemplate := getTestConfigTemplate(ctx, testConfigTemplateProp) 202 if moduleTemplate.Valid() { 203 template = moduleTemplate.String() 204 } 205 for _, config := range configs { 206 configStrings = append(configStrings, config.Config()) 207 } 208 extraConfigs := strings.Join(configStrings, fmt.Sprintf("\\n%s", test_xml_indent)) 209 extraConfigs = fmt.Sprintf("--extra-configs '%s'", extraConfigs) 210 211 ctx.Build(pctx, android.BuildParams{ 212 Rule: autogenInstrumentationTest, 213 Description: "test config", 214 Input: manifest, 215 Output: autogenPath, 216 Args: map[string]string{ 217 "name": ctx.ModuleName(), 218 "template": template, 219 "extraConfigs": extraConfigs, 220 }, 221 }) 222 return autogenPath 223 } 224 return path 225} 226 227var Bool = proptools.Bool 228var BoolDefault = proptools.BoolDefault 229