Move splitFileExt to the android package.

Both Rust and cc use this function, so move it over to android
package's util.go and export it.

Bug: 140734195
Test: m -j

Change-Id: Ibe8b7a94592e402468a027ad6027b187f29c8e07
diff --git a/android/util.go b/android/util.go
index e02cca1..0102442 100644
--- a/android/util.go
+++ b/android/util.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path/filepath"
 	"reflect"
 	"regexp"
 	"runtime"
@@ -292,3 +293,29 @@
 	}
 	return strings.HasPrefix(str, pat[:i]) && strings.HasSuffix(str, pat[i+1:])
 }
+
+var shlibVersionPattern = regexp.MustCompile("(?:\\.\\d+(?:svn)?)+")
+
+// splitFileExt splits a file name into root, suffix and ext. root stands for the file name without
+// the file extension and the version number (e.g. "libexample"). suffix stands for the
+// concatenation of the file extension and the version number (e.g. ".so.1.0"). ext stands for the
+// file extension after the version numbers are trimmed (e.g. ".so").
+func SplitFileExt(name string) (string, string, string) {
+	// Extract and trim the shared lib version number if the file name ends with dot digits.
+	suffix := ""
+	matches := shlibVersionPattern.FindAllStringIndex(name, -1)
+	if len(matches) > 0 {
+		lastMatch := matches[len(matches)-1]
+		if lastMatch[1] == len(name) {
+			suffix = name[lastMatch[0]:lastMatch[1]]
+			name = name[0:lastMatch[0]]
+		}
+	}
+
+	// Extract the file name root and the file extension.
+	ext := filepath.Ext(name)
+	root := strings.TrimSuffix(name, ext)
+	suffix = ext + suffix
+
+	return root, suffix, ext
+}
diff --git a/android/util_test.go b/android/util_test.go
index 2e5eb07..1df1c5a 100644
--- a/android/util_test.go
+++ b/android/util_test.go
@@ -404,3 +404,68 @@
 	// b = ["foo" "bar"]
 	// c = ["foo" "baz"]
 }
+
+func TestSplitFileExt(t *testing.T) {
+	t.Run("soname with version", func(t *testing.T) {
+		root, suffix, ext := SplitFileExt("libtest.so.1.0.30")
+		expected := "libtest"
+		if root != expected {
+			t.Errorf("root should be %q but got %q", expected, root)
+		}
+		expected = ".so.1.0.30"
+		if suffix != expected {
+			t.Errorf("suffix should be %q but got %q", expected, suffix)
+		}
+		expected = ".so"
+		if ext != expected {
+			t.Errorf("ext should be %q but got %q", expected, ext)
+		}
+	})
+
+	t.Run("soname with svn version", func(t *testing.T) {
+		root, suffix, ext := SplitFileExt("libtest.so.1svn")
+		expected := "libtest"
+		if root != expected {
+			t.Errorf("root should be %q but got %q", expected, root)
+		}
+		expected = ".so.1svn"
+		if suffix != expected {
+			t.Errorf("suffix should be %q but got %q", expected, suffix)
+		}
+		expected = ".so"
+		if ext != expected {
+			t.Errorf("ext should be %q but got %q", expected, ext)
+		}
+	})
+
+	t.Run("version numbers in the middle should be ignored", func(t *testing.T) {
+		root, suffix, ext := SplitFileExt("libtest.1.0.30.so")
+		expected := "libtest.1.0.30"
+		if root != expected {
+			t.Errorf("root should be %q but got %q", expected, root)
+		}
+		expected = ".so"
+		if suffix != expected {
+			t.Errorf("suffix should be %q but got %q", expected, suffix)
+		}
+		expected = ".so"
+		if ext != expected {
+			t.Errorf("ext should be %q but got %q", expected, ext)
+		}
+	})
+
+	t.Run("no known file extension", func(t *testing.T) {
+		root, suffix, ext := SplitFileExt("test.exe")
+		expected := "test"
+		if root != expected {
+			t.Errorf("root should be %q but got %q", expected, root)
+		}
+		expected = ".exe"
+		if suffix != expected {
+			t.Errorf("suffix should be %q but got %q", expected, suffix)
+		}
+		if ext != expected {
+			t.Errorf("ext should be %q but got %q", expected, ext)
+		}
+	})
+}