Merge "Add zucchini to otatools.zip"
diff --git a/core/product_config.rbc b/core/product_config.rbc
index f7ce7aa..ef0d0c9 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -373,9 +373,14 @@
         if type(val) == "list":
             val.append(_indirect(pcm_name))
 
+def __base(path):
+    """Returns basename."""
+    return path.rsplit("/",1)[-1]
+
+
 def _copy_files(l, outdir):
     """Generate <item>:<outdir>/item for each item."""
-    return ["%s:%s/%s" % (item, outdir, item) for item in __words(l)]
+    return ["%s:%s/%s" % (path, outdir, __base(path)) for path in __words(l)]
 
 def _copy_if_exists(path_pair):
     """If from file exists, returns [from:to] pair."""
@@ -396,7 +401,8 @@
 
 def _find_and_copy(pattern, from_dir, to_dir):
     """Return a copy list for the files matching the pattern."""
-    return ["%s/%s:%s/%s" % (from_dir, f, to_dir, f) for f in rblf_wildcard(pattern, from_dir)]
+    return ["%s/%s:%s/%s" % (
+        from_dir, f, to_dir, f) for f in rblf_find_files(from_dir, pattern, only_files=1)]
 
 def _filter_out(pattern, text):
     """Return all the words from `text' that do not match any word in `pattern'.
diff --git a/tests/device.rbc b/tests/device.rbc
index b57dbf9..feefcf7 100644
--- a/tests/device.rbc
+++ b/tests/device.rbc
@@ -45,9 +45,11 @@
   cfg["PRODUCT_COPY_FILES"] += ["device_from:device_to"]
   _include1_init(g, handle)
   cfg["PRODUCT_PACKAGES"] += ["dev_after"]
-  cfg["PRODUCT_COPY_FILES"] += (rblf.find_and_copy("audio_platform_info*.xml", "device/google/redfin/audio", "||VENDOR-PATH-PH||/etc") +
+  cfg["PRODUCT_COPY_FILES"] += (rblf.find_and_copy("audio_platform_info*.xml", "device/google/redfin", "||VENDOR-PATH-PH||/etc") +
       ["xyz:/etc/xyz"])
   cfg["PRODUCT_COPY_FILES"] += rblf.copy_files("x.xml y.xml", "/etc")
+  cfg["PRODUCT_COPY_FILES"] += rblf.copy_files(["from/sub/x", "from/sub/y"], "to")
+
   rblf.add_soong_config_namespace(g, "NS1")
   rblf.add_soong_config_var_value(g, "NS1", "v1", "abc")
   rblf.add_soong_config_var_value(g, "NS1", "v2", "def")
diff --git a/tests/run.rbc b/tests/run.rbc
index 35ae19d..4d7166a 100644
--- a/tests/run.rbc
+++ b/tests/run.rbc
@@ -53,10 +53,12 @@
       "PRODUCT_COPY_FILES": [
           "part_from:part_to",
           "device_from:device_to",
-          "device/google/redfin/audio/audio_platform_info_noextcodec_snd.xml:||VENDOR-PATH-PH||/etc/audio_platform_info_noextcodec_snd.xml",
+          "device/google/redfin/audio/audio_platform_info_noextcodec_snd.xml:||VENDOR-PATH-PH||/etc/audio/audio_platform_info_noextcodec_snd.xml",
           "xyz:/etc/xyz",
           "x.xml:/etc/x.xml",
           "y.xml:/etc/y.xml",
+          "from/sub/x:to/x",
+          "from/sub/y:to/y",
       ],
       "PRODUCT_HOST_PACKAGES": ["host"],
       "PRODUCT_PACKAGES": [
diff --git a/tools/rbcrun/README.md b/tools/rbcrun/README.md
index fb58c89..ecf8a24 100644
--- a/tools/rbcrun/README.md
+++ b/tools/rbcrun/README.md
@@ -68,6 +68,11 @@
 
 Returns `True`  if *file* exists
 
+#### rblf_find_files(*top*, *file-pattern*, only_files = 0)
+
+Returns all the paths under *top* whose basename matches *pattern* (which is a shell's glob pattern). If *only_files* is
+not zero, only the paths to the regular files are returned. The returned paths are relative to *top*.
+
 #### rblf_wildcard(*glob*, *top* = None)
 
 Expands *glob*. If *top* is supplied, expands "*top*/*glob*", then removes
diff --git a/tools/rbcrun/host.go b/tools/rbcrun/host.go
index b3dd499..7f4f72d 100644
--- a/tools/rbcrun/host.go
+++ b/tools/rbcrun/host.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"io/fs"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -170,6 +171,46 @@
 	return makeStringList(files), nil
 }
 
+// find(top, pattern, only_files = 0) returns all the paths under 'top'
+// whose basename matches 'pattern' (which is a shell's glob pattern).
+// If 'only_files' is non-zero, only the paths to the regular files are
+// returned. The returned paths are relative to 'top'.
+func find(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple,
+	kwargs []starlark.Tuple) (starlark.Value, error) {
+	var top, pattern string
+	var onlyFiles int
+	if err := starlark.UnpackArgs(b.Name(), args, kwargs,
+		"top", &top, "pattern", &pattern, "only_files?", &onlyFiles); err != nil {
+		return starlark.None, err
+	}
+	top = filepath.Clean(top)
+	pattern = filepath.Clean(pattern)
+	// Go's filepath.Walk is slow, consider using OS's find
+	var res []string
+	err := filepath.WalkDir(top, func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			if d != nil && d.IsDir() {
+				return fs.SkipDir
+			} else {
+				return nil
+			}
+		}
+		relPath := strings.TrimPrefix(path, top)
+		if len(relPath) > 0 && relPath[0] == os.PathSeparator {
+			relPath = relPath[1:]
+		}
+		// Do not return top-level dir
+		if len(relPath) == 0 {
+			return nil
+		}
+		if matched, err := filepath.Match(pattern, d.Name()); err == nil && matched && (onlyFiles == 0 || d.Type().IsRegular()) {
+			res = append(res, relPath)
+		}
+		return nil
+	})
+	return makeStringList(res), err
+}
+
 // shell(command) runs OS shell with given command and returns back
 // its output the same way as Make's $(shell ) function. The end-of-lines
 // ("\n" or "\r\n") are replaced with " " in the result, and the trailing
@@ -226,6 +267,8 @@
 		"rblf_env": structFromEnv(os.Environ()),
 		// To convert makefile's $(wildcard foo)
 		"rblf_file_exists": starlark.NewBuiltin("rblf_file_exists", fileExists),
+		// To convert find-copy-subdir and product-copy-files-by pattern
+		"rblf_find_files": starlark.NewBuiltin("rblf_find_files", find),
 		// To convert makefile's $(filter ...)/$(filter-out)
 		"rblf_regex": starlark.NewBuiltin("rblf_regex", regexMatch),
 		// To convert makefile's $(shell cmd)
diff --git a/tools/rbcrun/testdata/file_ops.star b/tools/rbcrun/testdata/file_ops.star
index 31631ef..50e39bf 100644
--- a/tools/rbcrun/testdata/file_ops.star
+++ b/tools/rbcrun/testdata/file_ops.star
@@ -9,11 +9,17 @@
     assert.true(not rblf_file_exists("no_such_file"), "the file no_such_file does not exist")
     files = rblf_wildcard("*.star")
     assert.true(myname in files, "expected %s in  %s" % (myname, files))
-    # RBCDATADIR is set by the caller to the path where this file resides
     files = rblf_wildcard("*.star", rblf_env.TEST_DATA_DIR)
     assert.true(myname in files, "expected %s in %s" % (myname, files))
     files = rblf_wildcard("*.xxx")
     assert.true(len(files) == 0, "expansion should be empty but contains %s" % files)
-
-
+    mydir = "testdata"
+    myrelname = "%s/%s" % (mydir, myname)
+    files = rblf_find_files(rblf_env.TEST_DATA_DIR + "/../", "*")
+    assert.true(mydir in files and myrelname in files, "expected %s and %s in %s" % (mydir, myrelname, files))
+    files = rblf_find_files(rblf_env.TEST_DATA_DIR + "/../", "*", only_files=1)
+    assert.true(mydir not in files, "did not expect %s in %s" % (mydir, files))
+    assert.true(myrelname in files, "expected %s  in %s" % (myrelname, files))
+    files = rblf_find_files(rblf_env.TEST_DATA_DIR + "/../", "*.star")
+    assert.true(myrelname in files, "expected %s in %s" % (myrelname, files))
 test()