diff --git a/vndk/tools/elfcheck/Android.bp b/vndk/tools/elfcheck/Android.bp new file mode 100644 index 000000000..2776d9243 --- /dev/null +++ b/vndk/tools/elfcheck/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +blueprint_go_binary { + name: "bpflatten", + srcs: [ + "bpflatten/main.go", + ], + deps: [ + "blueprint-parser", + ], +} + +sh_binary_host { + name: "fix_android_bp_prebuilt", + src: "fix_android_bp_prebuilt.sh", +} diff --git a/vndk/tools/elfcheck/bpflatten/main.go b/vndk/tools/elfcheck/bpflatten/main.go new file mode 100644 index 000000000..c4450b190 --- /dev/null +++ b/vndk/tools/elfcheck/bpflatten/main.go @@ -0,0 +1,229 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "os" + "strings" + + "github.com/google/blueprint/parser" +) + +type FlatModule struct { + Type string + Name string + PropertyMap map[string]interface{} +} + +func expandScalarTypeExpression(value parser.Expression) (scalar interface{}, isScalar bool) { + if s, ok := value.(*parser.Bool); ok { + return s.Value, true + } else if s, ok := value.(*parser.String); ok { + return s.Value, true + } else if s, ok := value.(*parser.Int64); ok { + return s.Value, true + } + return nil, false +} + +func populatePropertyMap(propMap map[string]interface{}, prefix string, m *parser.Map) { + for _, prop := range m.Properties { + name := prop.Name + if prefix != "" { + name = prefix + "." + name + } + value := prop.Value.Eval() + if s, isScalar := expandScalarTypeExpression(value); isScalar { + propMap[name] = s + } else if list, ok := value.(*parser.List); ok { + var l []interface{} + for _, v := range list.Values { + if s, isScalar := expandScalarTypeExpression(v.Eval()); isScalar { + l = append(l, s) + } + } + propMap[name] = l + } else if mm, ok := value.(*parser.Map); ok { + populatePropertyMap(propMap, name, mm) + } + } +} + +var anonymousModuleCount int + +func flattenModule(module *parser.Module) (flattened FlatModule) { + flattened.Type = module.Type + if prop, found := module.GetProperty("name"); found { + if value, ok := prop.Value.Eval().(*parser.String); ok { + flattened.Name = value.Value + } + } else { + flattened.Name = fmt.Sprintf("anonymous@<%d>", anonymousModuleCount) + anonymousModuleCount++ + } + flattened.PropertyMap = make(map[string]interface{}) + populatePropertyMap(flattened.PropertyMap, "", &module.Map) + return flattened +} + +func processFile(filename string, in io.Reader) ([]FlatModule, error) { + if in == nil { + if file, err := os.Open(filename); err != nil { + return nil, err + } else { + defer file.Close() + in = file + } + } + + ast, errs := parser.ParseAndEval(filename, in, &parser.Scope{}) + if len(errs) > 0 { + for _, err := range errs { + fmt.Fprintln(os.Stderr, err) + } + return nil, fmt.Errorf("%d parsing errors", len(errs)) + } + + var modules []FlatModule + for _, def := range ast.Defs { + if module, ok := def.(*parser.Module); ok { + modules = append(modules, flattenModule(module)) + } + } + return modules, nil +} + +func quoteBashString(s string) string { + return strings.ReplaceAll(s, "$", "\\$") +} + +func printBash(flatModules []FlatModule, w io.Writer) { + var moduleNameList []string + if len(flatModules) == 0 { + // Early bail out if we have nothing to output + return + } + fmt.Fprintf(w, "declare -a MODULE_NAMES\n") + fmt.Fprintf(w, "declare -A MODULE_TYPE_DICT\n") + fmt.Fprintf(w, "declare -A MODULE_PROP_KEYS_DICT\n") + fmt.Fprintf(w, "declare -A MODULE_PROP_VALUES_DICT\n") + fmt.Fprintf(w, "\n") + for _, module := range flatModules { + name := quoteBashString(module.Name) + moduleNameList = append(moduleNameList, name) + var modulePropKeys []string + for k := range module.PropertyMap { + modulePropKeys = append(modulePropKeys, k) + } + fmt.Fprintf(w, "MODULE_TYPE_DICT[%q]=%q\n", name, quoteBashString(module.Type)) + fmt.Fprintf(w, "MODULE_PROP_KEYS_DICT[%q]=%q\n", name, + quoteBashString(strings.Join(modulePropKeys, " "))) + for k, v := range module.PropertyMap { + var propValue string + if vl, ok := v.([]interface{}); ok { + var list []string + for _, s := range vl { + list = append(list, fmt.Sprintf("%v", s)) + } + propValue = fmt.Sprintf("%s", strings.Join(list, " ")) + } else { + propValue = fmt.Sprintf("%v", v) + } + key := name + ":" + quoteBashString(k) + fmt.Fprintf(w, "MODULE_PROP_VALUES_DICT[%q]=%q\n", key, quoteBashString(propValue)) + } + fmt.Fprintf(w, "\n") + } + fmt.Fprintf(w, "MODULE_NAMES=(\n") + for _, name := range moduleNameList { + fmt.Fprintf(w, " %q\n", name) + } + fmt.Fprintf(w, ")\n") +} + +var ( + outputBashFlag = flag.Bool("bash", false, "Output in bash format") + outputJsonFlag = flag.Bool("json", false, "Output in json format (this is the default)") + helpFlag = flag.Bool("help", false, "Display this message and exit") + exitCode = 0 +) + +func init() { + flag.Usage = usage +} + +func usage() { + fmt.Fprintf(os.Stderr, "Usage: %s [OPTION]... [FILE]...\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Flatten Android.bp to python friendly json text.\n") + fmt.Fprintf(os.Stderr, "If no file list is specified, read from standard input.\n") + fmt.Fprintf(os.Stderr, "\n") + flag.PrintDefaults() +} + +func main() { + defer func() { + if err := recover(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + exitCode = 1 + } + os.Exit(exitCode) + }() + + flag.Parse() + + if *helpFlag { + usage() + return + } + + flatModules := []FlatModule{} + + if flag.NArg() == 0 { + if modules, err := processFile("", os.Stdin); err != nil { + panic(err) + } else { + flatModules = append(flatModules, modules...) + } + } + + for _, pathname := range flag.Args() { + switch fileInfo, err := os.Stat(pathname); { + case err != nil: + panic(err) + case fileInfo.IsDir(): + panic(fmt.Errorf("%q is a directory", pathname)) + default: + if modules, err := processFile(pathname, nil); err != nil { + panic(err) + } else { + flatModules = append(flatModules, modules...) + } + } + } + + if *outputBashFlag { + printBash(flatModules, os.Stdout) + } else { + if b, err := json.MarshalIndent(flatModules, "", " "); err != nil { + panic(err) + } else { + fmt.Printf("%s\n", b) + } + } +} diff --git a/vndk/tools/elfcheck/fix_android_bp_prebuilt.sh b/vndk/tools/elfcheck/fix_android_bp_prebuilt.sh new file mode 100755 index 000000000..ba2cc35b0 --- /dev/null +++ b/vndk/tools/elfcheck/fix_android_bp_prebuilt.sh @@ -0,0 +1,190 @@ +#!/bin/bash +# +# Copyright (C) 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This program fixes prebuilt ELF check errors by updating the "shared_libs" +# fields in Android.bp. +# +# Example: +# $ source build/envsetup.sh +# $ m fix_android_bp_prebuilt bpflatten bpmodify +# $ fix_android_bp_prebuilt --in-place path_to_problematic_android_bp + +set -e + +function usage() { + cat <&2 "Unexpected flag: $1" + usage >&2 + exit 1 + ;; + esac + shift +done + +if ! [[ -f "$1" ]]; then + echo >&2 "No such file: '$1'" + exit 1 +fi + +if [[ -e "$(command -v llvm-readelf)" ]]; then + READELF="llvm-readelf" +elif [[ -e "$(command -v readelf)" ]]; then + READELF="readelf -W" +else + echo >&2 'Cannot find readelf in $PATH, please run:' + echo >&2 '$ source build/envsetup.sh' + exit 1 +fi + +if ! [[ -e "$(command -v bpflatten)" && -e "$(command -v bpmodify)" ]]; then + echo >&2 'Cannot find bpflatten and bpmodify in $PATH, please run:' + echo >&2 '$ source build/envsetup.sh' + echo >&2 '$ m blueprint_tools' + exit 1 +fi + +readonly EDIT_IN_PLACE +readonly SHOW_DIFF +readonly READELF +readonly ANDROID_BP="$1" +readonly ANDROID_BP_DIR=$(dirname "$ANDROID_BP") +readonly TEMP_DIR=$(mktemp -d) +readonly TEMP_ANDROID_BP="${TEMP_DIR}/Android.bp" + +cp -L "$ANDROID_BP" "$TEMP_ANDROID_BP" + +# This subshell and `eval` must be on separate lines, so that eval would not +# shadow the subshell's exit code. +# In other words, if `bpflatten` fails, we mustn't eval its output. +FLATTEN_COMMAND=$(bpflatten --bash "$ANDROID_BP") +eval "$FLATTEN_COMMAND" + +for MODULE_NAME in "${MODULE_NAMES[@]}" ; do + MODULE_TYPE="${MODULE_TYPE_DICT[${MODULE_NAME}]}" + if ! [[ "$MODULE_TYPE" =~ ^(.+_)?prebuilt(_.+)?$ ]]; then + continue + fi + + SRCS=$(get_prop "$MODULE_NAME" "srcs") + SHARED_LIBS=$(get_prop "$MODULE_NAME" "shared_libs") + if [[ -n "${SRCS}" ]]; then + DT_NEEDED=$(get_dt_needed "$SRCS") + if [[ $(unique "$DT_NEEDED") != $(unique "$SHARED_LIBS") ]]; then + rewrite_prop "$MODULE_NAME" "shared_libs" "$DT_NEEDED" + fi + fi + + # Handle different arch / target variants... + for PROP in ${MODULE_PROP_KEYS_DICT[${MODULE_NAME}]} ; do + if ! [[ "$PROP" =~ \.srcs$ ]]; then + continue + fi + SRCS=$(get_prop "$MODULE_NAME" "$PROP") + DT_NEEDED=$(get_dt_needed "$SRCS") + SHARED_LIBS_PROP="${PROP%.srcs}.shared_libs" + VARIANT_SHARED_LIBS="${SHARED_LIBS} $(get_prop "$MODULE_NAME" "$SHARED_LIBS_PROP")" + if [[ $(unique "$DT_NEEDED") != $(unique "$VARIANT_SHARED_LIBS") ]]; then + rewrite_prop "$MODULE_NAME" "$SHARED_LIBS_PROP" "$DT_NEEDED" + fi + done +done + +if [[ -n "$SHOW_DIFF" ]]; then + diff -u "$ANDROID_BP" "$TEMP_ANDROID_BP" || true +fi + +if [[ -n "$EDIT_IN_PLACE" ]]; then + cp "$TEMP_ANDROID_BP" "$ANDROID_BP" +fi + +if [[ -z "${SHOW_DIFF}${EDIT_IN_PLACE}" ]]; then + cat "$TEMP_ANDROID_BP" +fi