Mark ab/6881855 as merged

Bug: 172690556
Change-Id: I248930712914393022e6a51c15ffe92a789edc26
diff --git a/crates_updater.py b/crates_updater.py
index ba8f16f..78fa9b5 100644
--- a/crates_updater.py
+++ b/crates_updater.py
@@ -14,6 +14,9 @@
 """Module to check updates from crates.io."""
 
 import json
+import os
+# pylint: disable=g-importing-member
+from pathlib import Path
 import re
 import urllib.request
 
@@ -35,6 +38,10 @@
 
 VERSION_MATCHER: re.Pattern = re.compile(VERSION_PATTERN)
 
+DESCRIPTION_PATTERN: str = (r"^description *= *(\".+\")")
+
+DESCRIPTION_MATCHER: re.Pattern = re.compile(DESCRIPTION_PATTERN)
+
 
 class CratesUpdater(Updater):
     """Updater for crates.io packages."""
@@ -114,7 +121,8 @@
             urllib.request.urlcleanup()
 
     # pylint: disable=no-self-use
-    def update_metadata(self, metadata: metadata_pb2.MetaData) -> None:
+    def update_metadata(self, metadata: metadata_pb2.MetaData,
+                        full_path: Path) -> None:
         """Updates METADATA content."""
         # copy only HOMEPAGE url, and then add new ARCHIVE url.
         new_url_list = []
@@ -128,3 +136,30 @@
         new_url_list.append(new_url)
         del metadata.third_party.url[:]
         metadata.third_party.url.extend(new_url_list)
+        # copy description from Cargo.toml to METADATA
+        cargo_toml = os.path.join(full_path, "Cargo.toml")
+        description = self._get_cargo_description(cargo_toml)
+        if description and description != metadata.description:
+            print("New METADATA description:", description)
+            metadata.description = description
+
+    def _toml2str(self, line: str) -> str:
+        """Convert a quoted toml string to a Python str without quotes."""
+        if line.startswith("\"\"\""):
+            return ""  # cannot handle broken multi-line description
+        # TOML string escapes: \b \t \n \f \r \" \\ (no unicode escape)
+        line = line[1:-1].replace("\\\\", "\n").replace("\\b", "")
+        line = line.replace("\\t", " ").replace("\\n", " ").replace("\\f", " ")
+        line = line.replace("\\r", "").replace("\\\"", "\"").replace("\n", "\\")
+        # replace a unicode quotation mark, used in the libloading crate
+        return line.replace("’", "'").strip()
+
+    def _get_cargo_description(self, cargo_toml: str) -> str:
+        """Return the description in Cargo.toml or empty string."""
+        if os.path.isfile(cargo_toml) and os.access(cargo_toml, os.R_OK):
+            with open(cargo_toml, "r") as toml_file:
+                for line in toml_file:
+                    match = DESCRIPTION_MATCHER.match(line)
+                    if match:
+                        return self._toml2str(match.group(1))
+        return ""
diff --git a/external_updater.py b/external_updater.py
index 7aa5729..006ad34 100644
--- a/external_updater.py
+++ b/external_updater.py
@@ -109,7 +109,7 @@
             metadata_url.CopyFrom(updater.latest_url)
     # For Rust crates, replace GIT url with ARCHIVE url
     if isinstance(updater, CratesUpdater):
-        updater.update_metadata(updated_metadata)
+        updater.update_metadata(updated_metadata, full_path)
     fileutils.write_metadata(full_path, updated_metadata, args.keep_date)
     git_utils.add_file(full_path, 'METADATA')
 
diff --git a/regen_bp.sh b/regen_bp.sh
index df319a5..963442b 100755
--- a/regen_bp.sh
+++ b/regen_bp.sh
@@ -14,70 +14,127 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This script is used by external_updater to replace a package. Don't
-# invoke directly.
-
-set -e
-
-# Call this in two ways:
-# (1) in a .../external/* rust directory with .bp and Cargo.toml,
-#     development/scripts/cargo2android.py must be in PATH
+# This script is used by external_updater to replace a package.
+# It can also be invoked directly.  It is used in two ways:
+# (1) in a .../external/* rust directory with .bp and Cargo.toml;
+#     cargo2android.py must be in PATH
 # (2) in a tmp new directory with .bp and Cargo.toml,
 #     and $1 equals to the rust Android source tree root,
 #     and $2 equals to the rust sub-directory path name under external.
-if [ "$1" == "" ]; then
-  external_dir=`pwd`
-  C2A=`which cargo2android.py`
-  if [ "$C2A" == "" ]; then
-    echo "ERROR: cannot find cargo2android.py in PATH"
-    exit 1
+
+set -e
+
+function main() {
+  check_files $*
+  update_files_with_cargo_pkg_vars
+  # Save Cargo.lock if it existed before this update.
+  [ ! -f Cargo.lock ] || mv Cargo.lock Cargo.lock.saved
+  echo "Updating Android.bp: $C2A $FLAGS"
+  $C2A $FLAGS
+  copy_cargo_out_files $*
+  rm -rf target.tmp cargo.out Cargo.lock
+  # Restore Cargo.lock if it existed before this update.
+  [ ! -f Cargo.lock.saved ] || mv Cargo.lock.saved Cargo.lock
+}
+
+function abort() {
+  echo "$1" >&2
+  exit 1
+}
+
+function check_files() {
+  if [ "$1" == "" ]; then
+    EXTERNAL_DIR=`pwd`
+    C2A=`which cargo2android.py ||
+         abort "ERROR: cannot find cargo2android.py in PATH"`
+  else
+    EXTERNAL_DIR="$2"  # e.g. rust/crates/bytes
+    C2A="$1/development/scripts/cargo2android.py"
+    [ -f "$C2A" ] || abort "ERROR: cannot find $C2A"
   fi
-else
-  external_dir="$2"  # e.g. rust/crates/bytes
-  C2A="$1/development/scripts/cargo2android.py"
-  if [ ! -f $C2A ]; then
-    echo "ERROR: cannot find $C2A"
-    exit 1
+  LINE1=`head -1 Android.bp || abort "ERROR: cannot find Android.bp"`
+  if [[ ! "$LINE1" =~ ^.*cargo2android.py.*$ ]]; then
+    echo 'Android.bp header does not contain "cargo2android.py"; skip regen_bp'
+    exit 0
   fi
-fi
+  FLAGS=`echo "$LINE1" | sed -e 's:^.*cargo2android.py ::;s:\.$::'`
+  [ -f Cargo.toml ] || abort "ERROR: cannot find ./Cargo.toml."
+}
 
-# Save Cargo.lock if it existed before this update.
-if [ -f Cargo.lock ]; then
-  mv Cargo.lock Cargo.lock.saved
-fi
+function copy_cargo_out_files() {
+  if [ -d $2/out ]; then
+    # copy files generated by cargo build to out directory
+    PKGNAME=`basename $2`
+    for f in $2/out/*
+    do
+      OUTF=`basename $f`
+      SRC=`ls ./target.tmp/*/debug/build/$PKGNAME-*/out/$OUTF ||
+           ls ./target.tmp/debug/build/$PKGNAME-*/out/$OUTF || true`
+      if [ "$SRC" != "" ]; then
+        echo "Copying $SRC to out/$OUTF"
+        mkdir -p out
+        cp $SRC out/$OUTF
+      fi
+    done
+  fi
+}
 
-LINE1=`head -1 Android.bp`
-FLAGS=`echo $LINE1 | sed -e 's:^.*cargo2android.py ::;s:\.$::'`
-CMD="$C2A $FLAGS"
-echo "Updating Android.bp: $CMD"
-$CMD
+function update_files_with_cargo_pkg_vars() {
+  FILES=`grep -r -l --include \*.rs \
+    --exclude-dir .git --exclude build.rs \
+    --exclude-dir target.tmp --exclude-dir target \
+    -E 'env!\("CARGO_PKG_(NAME|VERSION|AUTHORS|DESCRIPTION)"\)' * || true`
+  if [ "$FILES" != "" ]; then
+    printf "INFO: to update FILES: %s\n" "`echo ${FILES} | paste -s -d' '`"
+    # Find in ./Cargo.toml the 'name', 'version', 'authors', 'description'
+    # strings and use them to replace env!("CARGO_PKG_*") in $FILES.
+    grep_cargo_key_values
+    update_files
+  fi
+}
 
-if [ -d $2/out ]; then
-  # copy files generated by cargo build to out directory
-  PKGNAME=`basename $2`
-  for f in $2/out/*
-  do
-    OUTF=`basename $f`
-    SRC=`ls ./target.tmp/debug/build/$PKGNAME-*/out/$OUTF || true`
-    if [ "$SRC" != "" ]; then
-      echo "Copying $SRC to out/$OUTF"
-      mkdir -p out
-      cp $SRC out/$OUTF
-    fi
-  done
-fi
-rm -rf target.tmp cargo.out Cargo.lock
+function grep_one_key_value()
+{
+  # Grep the first key $1 in Cargo.toml and return its value.
+  grep "^$1 = " Cargo.toml | head -1 | sed -e "s:^$1 = ::" \
+    || abort "ERROR: Cannot find '$1' in ./Cargo.toml"
+}
 
-# Restore Cargo.lock if it existed before this update.
-if [ -f Cargo.lock.saved ]; then
-  mv Cargo.lock.saved Cargo.lock
-fi
+function grep_cargo_key_values()
+{
+  NAME=`grep_one_key_value name`
+  VERSION=`grep_one_key_value version`
+  AUTHORS=`grep_one_key_value authors`
+  DESCRIPTION=`grep_one_key_value description`
+  if [ "$DESCRIPTION" == "\"\"\"" ]; then
+    # Old Cargo.toml description format, found only in the 'shlex' crate.
+    DESCRIPTION=`printf '"%s-%s"' "$NAME" "$VERSION"`
+    printf "WARNING: use %s for its CARGO_PKG_DESCRIPTION." "$DESCRIPTION"
+  fi
+  # CARGO_PKG_AUTHORS uses ':' as the separator.
+  AUTHORS="$AUTHORS.join(\":\")"
+}
 
-# Some .bp files have manual changes that cannot be fixed by post_update.sh.
-# Add a note to force a manual edit.
-case $external_dir in
-  */libloading|*/libsqlite3-sys|*/unicode-xid)
-    echo "FIXME: Copy manual changes from old version!" >> Android.bp
-esac
+function build_sed_cmd()
+{
+  # Replace '\' with '\\' to keep escape sequence in the sed command.
+  # NAME and VERSION are simple stings without escape sequence.
+  s1=`printf "$1" "NAME" "$NAME"`
+  s2=`printf "$1" "VERSION" "$VERSION"`
+  s3=`printf "$1" "AUTHORS" "${AUTHORS//\\\\/\\\\\\\\}"`
+  s4=`printf "$1" "DESCRIPTION" "${DESCRIPTION//\\\\/\\\\\\\\}"`
+  echo "$s1;$s2;$s3;$s4"
+}
 
-exit 0
+function update_files()
+{
+  # Replace option_env!("...") with Some("...")
+  # Replace env!("...") with string literal "..."
+  # Do not replace run-time std::env::var("....") with
+  #   (Ok("...".to_string()) as std::result::Result<...>)
+  local cmd=`build_sed_cmd 's%%option_env!("CARGO_PKG_%s")%%Some(%s)%%g'`
+  cmd="$cmd;"`build_sed_cmd 's%%env!("CARGO_PKG_%s")%%%s%%g'`
+  sed -i -e "$cmd" $FILES
+}
+
+main $*