blob: 2549f1114f1396d5b659186b198164b2e9b7ec8c [file] [log] [blame]
Cole Faustb85d1a12022-11-08 18:14:01 -08001package bp2build
2
3import (
Cole Faustf8231dd2023-04-21 17:37:11 -07004 "android/soong/android"
Cole Faust87c0c332023-07-31 12:10:12 -07005 "android/soong/android/soongconfig"
Cole Faustf8231dd2023-04-21 17:37:11 -07006 "android/soong/starlark_import"
7 "encoding/json"
Cole Faustb85d1a12022-11-08 18:14:01 -08008 "fmt"
9 "os"
10 "path/filepath"
Cole Faustf055db62023-07-24 15:17:03 -070011 "reflect"
Cole Faustb85d1a12022-11-08 18:14:01 -080012 "strings"
Cole Faustf8231dd2023-04-21 17:37:11 -070013
14 "github.com/google/blueprint/proptools"
15 "go.starlark.net/starlark"
Cole Faustb85d1a12022-11-08 18:14:01 -080016)
17
18func CreateProductConfigFiles(
Cole Faustf8231dd2023-04-21 17:37:11 -070019 ctx *CodegenContext) ([]BazelFile, []BazelFile, error) {
Cole Faustb85d1a12022-11-08 18:14:01 -080020 cfg := &ctx.config
21 targetProduct := "unknown"
22 if cfg.HasDeviceProduct() {
23 targetProduct = cfg.DeviceProduct()
24 }
25 targetBuildVariant := "user"
26 if cfg.Eng() {
27 targetBuildVariant = "eng"
28 } else if cfg.Debuggable() {
29 targetBuildVariant = "userdebug"
30 }
31
32 productVariablesFileName := cfg.ProductVariablesFileName
33 if !strings.HasPrefix(productVariablesFileName, "/") {
34 productVariablesFileName = filepath.Join(ctx.topDir, productVariablesFileName)
35 }
Cole Faustf8231dd2023-04-21 17:37:11 -070036 productVariablesBytes, err := os.ReadFile(productVariablesFileName)
Cole Faustb85d1a12022-11-08 18:14:01 -080037 if err != nil {
Cole Faustf8231dd2023-04-21 17:37:11 -070038 return nil, nil, err
39 }
40 productVariables := android.ProductVariables{}
41 err = json.Unmarshal(productVariablesBytes, &productVariables)
42 if err != nil {
43 return nil, nil, err
Cole Faustb85d1a12022-11-08 18:14:01 -080044 }
45
46 // TODO(b/249685973): the name is product_config_platforms because product_config
47 // was already used for other files. Deduplicate them.
48 currentProductFolder := fmt.Sprintf("product_config_platforms/products/%s-%s", targetProduct, targetBuildVariant)
49
50 productReplacer := strings.NewReplacer(
51 "{PRODUCT}", targetProduct,
52 "{VARIANT}", targetBuildVariant,
53 "{PRODUCT_FOLDER}", currentProductFolder)
54
Cole Faust87c0c332023-07-31 12:10:12 -070055 platformMappingContent, err := platformMappingContent(productReplacer.Replace("@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}"), &productVariables, ctx.Config().Bp2buildSoongConfigDefinitions)
Cole Faustf8231dd2023-04-21 17:37:11 -070056 if err != nil {
57 return nil, nil, err
58 }
59
60 injectionDirFiles := []BazelFile{
Cole Faustb85d1a12022-11-08 18:14:01 -080061 newFile(
62 currentProductFolder,
63 "soong.variables.bzl",
Cole Faustf8231dd2023-04-21 17:37:11 -070064 `variables = json.decode("""`+strings.ReplaceAll(string(productVariablesBytes), "\\", "\\\\")+`""")`),
Cole Faustb85d1a12022-11-08 18:14:01 -080065 newFile(
66 currentProductFolder,
67 "BUILD",
68 productReplacer.Replace(`
69package(default_visibility=[
70 "@soong_injection//product_config_platforms:__subpackages__",
71 "@//build/bazel/product_config:__subpackages__",
72])
73load(":soong.variables.bzl", _soong_variables = "variables")
Cole Faustbd249822023-03-24 16:03:43 -070074load("@//build/bazel/product_config:android_product.bzl", "android_product")
Cole Faustb85d1a12022-11-08 18:14:01 -080075
76android_product(
77 name = "{PRODUCT}-{VARIANT}",
78 soong_variables = _soong_variables,
79)
80`)),
81 newFile(
82 "product_config_platforms",
83 "BUILD.bazel",
84 productReplacer.Replace(`
85package(default_visibility = [
86 "@//build/bazel/product_config:__subpackages__",
87 "@soong_injection//product_config_platforms:__subpackages__",
88])
Jingwen Chen583ab212023-05-30 09:45:23 +000089
90load("//{PRODUCT_FOLDER}:soong.variables.bzl", _soong_variables = "variables")
91load("@//build/bazel/product_config:android_product.bzl", "android_product")
92
Cole Faust319abae2023-06-06 15:12:49 -070093# Bazel will qualify its outputs by the platform name. When switching between products, this
94# means that soong-built files that depend on bazel-built files will suddenly get different
95# dependency files, because the path changes, and they will be rebuilt. In order to avoid this
96# extra rebuilding, make mixed builds always use a single platform so that the bazel artifacts
97# are always under the same path.
Jingwen Chen583ab212023-05-30 09:45:23 +000098android_product(
Cole Faust319abae2023-06-06 15:12:49 -070099 name = "mixed_builds_product-{VARIANT}",
Jingwen Chen583ab212023-05-30 09:45:23 +0000100 soong_variables = _soong_variables,
Cole Faustbc65a3f2023-08-01 16:38:55 +0000101 extra_constraints = ["@//build/bazel/platforms:mixed_builds"],
Jingwen Chen583ab212023-05-30 09:45:23 +0000102)
Cole Faust117bb742023-03-29 14:46:20 -0700103`)),
104 newFile(
105 "product_config_platforms",
106 "product_labels.bzl",
107 productReplacer.Replace(`
108# This file keeps a list of all the products in the android source tree, because they're
109# discovered as part of a preprocessing step before bazel runs.
110# TODO: When we start generating the platforms for more than just the
111# currently lunched product, they should all be listed here
112product_labels = [
Cole Faust319abae2023-06-06 15:12:49 -0700113 "@soong_injection//product_config_platforms:mixed_builds_product-{VARIANT}",
Cole Faust117bb742023-03-29 14:46:20 -0700114 "@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}"
115]
Cole Faustb85d1a12022-11-08 18:14:01 -0800116`)),
117 newFile(
118 "product_config_platforms",
119 "common.bazelrc",
120 productReplacer.Replace(`
Cole Faustf8231dd2023-04-21 17:37:11 -0700121build --platform_mappings=platform_mappings
Cole Faust319abae2023-06-06 15:12:49 -0700122build --platforms @soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}_linux_x86_64
Cole Faustb85d1a12022-11-08 18:14:01 -0800123
Cole Faust319abae2023-06-06 15:12:49 -0700124build:android --platforms=@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}
Cole Faustd1acaa42023-08-03 17:04:03 -0700125build:linux_x86 --platforms=@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}_linux_x86
Cole Faust319abae2023-06-06 15:12:49 -0700126build:linux_x86_64 --platforms=@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}_linux_x86_64
127build:linux_bionic_x86_64 --platforms=@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}_linux_bionic_x86_64
128build:linux_musl_x86 --platforms=@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}_linux_musl_x86
129build:linux_musl_x86_64 --platforms=@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}_linux_musl_x86_64
Cole Faustb85d1a12022-11-08 18:14:01 -0800130`)),
131 newFile(
132 "product_config_platforms",
133 "linux.bazelrc",
134 productReplacer.Replace(`
Cole Faust319abae2023-06-06 15:12:49 -0700135build --host_platform @soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}_linux_x86_64
Cole Faustb85d1a12022-11-08 18:14:01 -0800136`)),
137 newFile(
138 "product_config_platforms",
139 "darwin.bazelrc",
140 productReplacer.Replace(`
Cole Faust319abae2023-06-06 15:12:49 -0700141build --host_platform @soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}_darwin_x86_64
Cole Faustb85d1a12022-11-08 18:14:01 -0800142`)),
143 }
Cole Faustf8231dd2023-04-21 17:37:11 -0700144 bp2buildDirFiles := []BazelFile{
145 newFile(
146 "",
147 "platform_mappings",
148 platformMappingContent),
149 }
150 return injectionDirFiles, bp2buildDirFiles, nil
151}
Cole Faustb85d1a12022-11-08 18:14:01 -0800152
Cole Faust87c0c332023-07-31 12:10:12 -0700153func platformMappingContent(mainProductLabel string, mainProductVariables *android.ProductVariables, soongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions) (string, error) {
Cole Faustf8231dd2023-04-21 17:37:11 -0700154 productsForTesting, err := starlark_import.GetStarlarkValue[map[string]map[string]starlark.Value]("products_for_testing")
155 if err != nil {
156 return "", err
157 }
Cole Faustf055db62023-07-24 15:17:03 -0700158 var result strings.Builder
159 result.WriteString("platforms:\n")
Cole Faust87c0c332023-07-31 12:10:12 -0700160 platformMappingSingleProduct(mainProductLabel, mainProductVariables, soongConfigDefinitions, &result)
Cole Faustf8231dd2023-04-21 17:37:11 -0700161 for product, productVariablesStarlark := range productsForTesting {
162 productVariables, err := starlarkMapToProductVariables(productVariablesStarlark)
163 if err != nil {
164 return "", err
165 }
Cole Faust87c0c332023-07-31 12:10:12 -0700166 platformMappingSingleProduct("@//build/bazel/tests/products:"+product, &productVariables, soongConfigDefinitions, &result)
Cole Faustf8231dd2023-04-21 17:37:11 -0700167 }
Cole Faustf055db62023-07-24 15:17:03 -0700168 return result.String(), nil
Cole Faustf8231dd2023-04-21 17:37:11 -0700169}
170
Cole Faust88c8efb2023-07-18 11:05:16 -0700171var bazelPlatformSuffixes = []string{
172 "",
173 "_darwin_arm64",
174 "_darwin_x86_64",
175 "_linux_bionic_arm64",
176 "_linux_bionic_x86_64",
177 "_linux_musl_x86",
178 "_linux_musl_x86_64",
179 "_linux_x86",
180 "_linux_x86_64",
181 "_windows_x86",
182 "_windows_x86_64",
183}
184
Cole Faust87c0c332023-07-31 12:10:12 -0700185func platformMappingSingleProduct(label string, productVariables *android.ProductVariables, soongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions, result *strings.Builder) {
Cole Faustf055db62023-07-24 15:17:03 -0700186 targetBuildVariant := "user"
187 if proptools.Bool(productVariables.Eng) {
188 targetBuildVariant = "eng"
189 } else if proptools.Bool(productVariables.Debuggable) {
190 targetBuildVariant = "userdebug"
Cole Faustf8231dd2023-04-21 17:37:11 -0700191 }
Cole Faustf055db62023-07-24 15:17:03 -0700192
Cole Faust95c5cf82023-08-03 13:49:27 -0700193 platform_sdk_version := -1
194 if productVariables.Platform_sdk_version != nil {
195 platform_sdk_version = *productVariables.Platform_sdk_version
196 }
197
Cole Faustf055db62023-07-24 15:17:03 -0700198 for _, suffix := range bazelPlatformSuffixes {
199 result.WriteString(" ")
200 result.WriteString(label)
201 result.WriteString(suffix)
202 result.WriteString("\n")
203 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:always_use_prebuilt_sdks=%t\n", proptools.Bool(productVariables.Always_use_prebuilt_sdks)))
Cole Faust87c0c332023-07-31 12:10:12 -0700204 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:arc=%t\n", proptools.Bool(productVariables.Arc)))
Cole Faustf055db62023-07-24 15:17:03 -0700205 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:apex_global_min_sdk_version_override=%s\n", proptools.String(productVariables.ApexGlobalMinSdkVersionOverride)))
Cole Faust87c0c332023-07-31 12:10:12 -0700206 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:binder32bit=%t\n", proptools.Bool(productVariables.Binder32bit)))
207 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:build_from_text_stub=%t\n", proptools.Bool(productVariables.Build_from_text_stub)))
Cole Faustf055db62023-07-24 15:17:03 -0700208 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:build_id=%s\n", proptools.String(productVariables.BuildId)))
209 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:build_version_tags=%s\n", strings.Join(productVariables.BuildVersionTags, ",")))
210 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:certificate_overrides=%s\n", strings.Join(productVariables.CertificateOverrides, ",")))
211 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:cfi_exclude_paths=%s\n", strings.Join(productVariables.CFIExcludePaths, ",")))
212 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:cfi_include_paths=%s\n", strings.Join(productVariables.CFIIncludePaths, ",")))
213 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:compressed_apex=%t\n", proptools.Bool(productVariables.CompressedApex)))
Cole Faust87c0c332023-07-31 12:10:12 -0700214 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:debuggable=%t\n", proptools.Bool(productVariables.Debuggable)))
Cole Faustf055db62023-07-24 15:17:03 -0700215 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:default_app_certificate=%s\n", proptools.String(productVariables.DefaultAppCertificate)))
216 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_abi=%s\n", strings.Join(productVariables.DeviceAbi, ",")))
217 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_max_page_size_supported=%s\n", proptools.String(productVariables.DeviceMaxPageSizeSupported)))
218 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_name=%s\n", proptools.String(productVariables.DeviceName)))
Cole Faust87c0c332023-07-31 12:10:12 -0700219 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_page_size_agnostic=%t\n", proptools.Bool(productVariables.Device_page_size_agnostic)))
Cole Faustf055db62023-07-24 15:17:03 -0700220 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_product=%s\n", proptools.String(productVariables.DeviceProduct)))
221 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:enable_cfi=%t\n", proptools.BoolDefault(productVariables.EnableCFI, true)))
Cole Faust87c0c332023-07-31 12:10:12 -0700222 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:enforce_vintf_manifest=%t\n", proptools.Bool(productVariables.Enforce_vintf_manifest)))
223 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:eng=%t\n", proptools.Bool(productVariables.Eng)))
224 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:malloc_not_svelte=%t\n", proptools.Bool(productVariables.Malloc_not_svelte)))
225 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:malloc_pattern_fill_contents=%t\n", proptools.Bool(productVariables.Malloc_pattern_fill_contents)))
226 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:malloc_zero_contents=%t\n", proptools.Bool(productVariables.Malloc_zero_contents)))
Cole Faustf055db62023-07-24 15:17:03 -0700227 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:manifest_package_name_overrides=%s\n", strings.Join(productVariables.ManifestPackageNameOverrides, ",")))
Cole Faust87c0c332023-07-31 12:10:12 -0700228 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:native_coverage=%t\n", proptools.Bool(productVariables.Native_coverage)))
Cole Faustf055db62023-07-24 15:17:03 -0700229 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:platform_version_name=%s\n", proptools.String(productVariables.Platform_version_name)))
230 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:product_brand=%s\n", productVariables.ProductBrand))
231 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:product_manufacturer=%s\n", productVariables.ProductManufacturer))
Cole Faust95c5cf82023-08-03 13:49:27 -0700232 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:platform_sdk_version=%d\n", platform_sdk_version))
Cole Faust87c0c332023-07-31 12:10:12 -0700233 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:safestack=%t\n", proptools.Bool(productVariables.Safestack)))
Cole Faustf055db62023-07-24 15:17:03 -0700234 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:target_build_variant=%s\n", targetBuildVariant))
Cole Faust87c0c332023-07-31 12:10:12 -0700235 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:treble_linker_namespaces=%t\n", proptools.Bool(productVariables.Treble_linker_namespaces)))
Cole Faustf055db62023-07-24 15:17:03 -0700236 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:tidy_checks=%s\n", proptools.String(productVariables.TidyChecks)))
Cole Faust87c0c332023-07-31 12:10:12 -0700237 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:uml=%t\n", proptools.Bool(productVariables.Uml)))
Cole Faustf055db62023-07-24 15:17:03 -0700238 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:unbundled_build=%t\n", proptools.Bool(productVariables.Unbundled_build)))
239 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:unbundled_build_apps=%s\n", strings.Join(productVariables.Unbundled_build_apps, ",")))
Cole Faust87c0c332023-07-31 12:10:12 -0700240 for namespace, namespaceContents := range productVariables.VendorVars {
241 for variable, value := range namespaceContents {
242 key := namespace + "__" + variable
243 _, hasBool := soongConfigDefinitions.BoolVars[key]
244 _, hasString := soongConfigDefinitions.StringVars[key]
245 _, hasValue := soongConfigDefinitions.ValueVars[key]
246 if !hasBool && !hasString && !hasValue {
247 // Not all soong config variables are defined in Android.bp files. For example,
248 // prebuilt_bootclasspath_fragment uses soong config variables in a nonstandard
249 // way, that causes them to be present in the soong.variables file but not
250 // defined in an Android.bp file. There's also nothing stopping you from setting
251 // a variable in make that doesn't exist in soong. We only generate build
252 // settings for the ones that exist in soong, so skip all others.
253 continue
254 }
255 if hasBool && hasString || hasBool && hasValue || hasString && hasValue {
256 panic(fmt.Sprintf("Soong config variable %s:%s appears to be of multiple types. bool? %t, string? %t, value? %t", namespace, variable, hasBool, hasString, hasValue))
257 }
258 if hasBool {
259 // Logic copied from soongConfig.Bool()
260 value = strings.ToLower(value)
261 if value == "1" || value == "y" || value == "yes" || value == "on" || value == "true" {
262 value = "true"
263 } else {
264 value = "false"
265 }
266 }
267 result.WriteString(fmt.Sprintf(" --//build/bazel/product_config/soong_config_variables:%s=%s\n", strings.ToLower(key), value))
268 }
269 }
Cole Faustf055db62023-07-24 15:17:03 -0700270 }
Cole Faustf8231dd2023-04-21 17:37:11 -0700271}
272
273func starlarkMapToProductVariables(in map[string]starlark.Value) (android.ProductVariables, error) {
Cole Faustf8231dd2023-04-21 17:37:11 -0700274 result := android.ProductVariables{}
Cole Faustf055db62023-07-24 15:17:03 -0700275 productVarsReflect := reflect.ValueOf(&result).Elem()
276 for i := 0; i < productVarsReflect.NumField(); i++ {
277 field := productVarsReflect.Field(i)
278 fieldType := productVarsReflect.Type().Field(i)
279 name := fieldType.Name
Cole Faust87c0c332023-07-31 12:10:12 -0700280 if name == "BootJars" || name == "ApexBootJars" || name == "VendorSnapshotModules" ||
281 name == "RecoverySnapshotModules" {
Cole Faustf055db62023-07-24 15:17:03 -0700282 // These variables have more complicated types, and we don't need them right now
283 continue
284 }
285 if _, ok := in[name]; ok {
Cole Faust87c0c332023-07-31 12:10:12 -0700286 if name == "VendorVars" {
287 vendorVars, err := starlark_import.Unmarshal[map[string]map[string]string](in[name])
288 if err != nil {
289 return result, err
290 }
291 field.Set(reflect.ValueOf(vendorVars))
292 continue
293 }
Cole Faustf055db62023-07-24 15:17:03 -0700294 switch field.Type().Kind() {
295 case reflect.Bool:
296 val, err := starlark_import.Unmarshal[bool](in[name])
297 if err != nil {
298 return result, err
299 }
300 field.SetBool(val)
301 case reflect.String:
302 val, err := starlark_import.Unmarshal[string](in[name])
303 if err != nil {
304 return result, err
305 }
306 field.SetString(val)
307 case reflect.Slice:
308 if field.Type().Elem().Kind() != reflect.String {
309 return result, fmt.Errorf("slices of types other than strings are unimplemented")
310 }
311 val, err := starlark_import.UnmarshalReflect(in[name], field.Type())
312 if err != nil {
313 return result, err
314 }
315 field.Set(val)
316 case reflect.Pointer:
317 switch field.Type().Elem().Kind() {
318 case reflect.Bool:
319 val, err := starlark_import.UnmarshalNoneable[bool](in[name])
320 if err != nil {
321 return result, err
322 }
323 field.Set(reflect.ValueOf(val))
324 case reflect.String:
325 val, err := starlark_import.UnmarshalNoneable[string](in[name])
326 if err != nil {
327 return result, err
328 }
329 field.Set(reflect.ValueOf(val))
330 case reflect.Int:
331 val, err := starlark_import.UnmarshalNoneable[int](in[name])
332 if err != nil {
333 return result, err
334 }
335 field.Set(reflect.ValueOf(val))
336 default:
337 return result, fmt.Errorf("pointers of types other than strings/bools are unimplemented: %s", field.Type().Elem().Kind().String())
338 }
339 default:
340 return result, fmt.Errorf("unimplemented type: %s", field.Type().String())
341 }
342 }
Cole Faust88c8efb2023-07-18 11:05:16 -0700343 }
Cole Faustf055db62023-07-24 15:17:03 -0700344
Cole Faust87c0c332023-07-31 12:10:12 -0700345 result.Native_coverage = proptools.BoolPtr(
346 proptools.Bool(result.GcovCoverage) ||
347 proptools.Bool(result.ClangCoverage))
348
Cole Faustb85d1a12022-11-08 18:14:01 -0800349 return result, nil
350}