Add a --symlinks argument to soong_zip

Add a --symlinks argument that defaults to true to soong_zip.
Passing --symlinks=false will cause it to follow symlinks instead
of storing them in the zip file.

Relands I4deb98daa9d4ba9f94e3d7670c117fe00381d2ba with tests.

Bug: 112843624
Test: glob_test.go
Test: zip_test.go
Test: m checkbuild
Change-Id: I0eff9c1f2dba79e873fda381ff585df55d5aaaad
diff --git a/zip/cmd/main.go b/zip/cmd/main.go
index 1125602..4a08491 100644
--- a/zip/cmd/main.go
+++ b/zip/cmd/main.go
@@ -137,6 +137,8 @@
 	emulateJar := flags.Bool("jar", false, "modify the resultant .zip to emulate the output of 'jar'")
 	writeIfChanged := flags.Bool("write_if_changed", false, "only update resultant .zip if it has changed")
 
+	symlinks := flags.Bool("symlinks", true, "store symbolic links in zip instead of following them")
+
 	parallelJobs := flags.Int("parallel", runtime.NumCPU(), "number of parallel threads to use")
 	cpuProfile := flags.String("cpuprofile", "", "write cpu profile to file")
 	traceFile := flags.String("trace", "", "write trace to file")
@@ -197,9 +199,10 @@
 		NumParallelJobs:          *parallelJobs,
 		NonDeflatedFiles:         nonDeflatedFiles,
 		WriteIfChanged:           *writeIfChanged,
+		StoreSymlinks:            *symlinks,
 	})
 	if err != nil {
-		fmt.Fprintln(os.Stderr, err.Error())
+		fmt.Fprintln(os.Stderr, "error:", err.Error())
 		os.Exit(1)
 	}
 }
diff --git a/zip/zip.go b/zip/zip.go
index 7eebf06..d8507df 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -188,6 +188,8 @@
 	compressorPool sync.Pool
 	compLevel      int
 
+	followSymlinks pathtools.ShouldFollowSymlinks
+
 	fs pathtools.FileSystem
 }
 
@@ -212,7 +214,9 @@
 	NumParallelJobs          int
 	NonDeflatedFiles         map[string]bool
 	WriteIfChanged           bool
-	Filesystem               pathtools.FileSystem
+	StoreSymlinks            bool
+
+	Filesystem pathtools.FileSystem
 }
 
 const NOQUOTE = '\x00'
@@ -263,13 +267,17 @@
 		args.AddDirectoryEntriesToZip = true
 	}
 
+	// Have Glob follow symlinks if they are not being stored as symlinks in the zip file.
+	followSymlinks := pathtools.ShouldFollowSymlinks(!args.StoreSymlinks)
+
 	z := &ZipWriter{
-		time:         jar.DefaultTime,
-		createdDirs:  make(map[string]string),
-		createdFiles: make(map[string]string),
-		directories:  args.AddDirectoryEntriesToZip,
-		compLevel:    args.CompressionLevel,
-		fs:           args.Filesystem,
+		time:           jar.DefaultTime,
+		createdDirs:    make(map[string]string),
+		createdFiles:   make(map[string]string),
+		directories:    args.AddDirectoryEntriesToZip,
+		compLevel:      args.CompressionLevel,
+		followSymlinks: followSymlinks,
+		fs:             args.Filesystem,
 	}
 
 	if z.fs == nil {
@@ -288,7 +296,7 @@
 				continue
 			}
 
-			globbed, _, err := z.fs.Glob(s, nil, pathtools.DontFollowSymlinks)
+			globbed, _, err := z.fs.Glob(s, nil, followSymlinks)
 			if err != nil {
 				return err
 			}
@@ -317,7 +325,7 @@
 					Err:  syscall.ENOTDIR,
 				}
 			}
-			globbed, _, err := z.fs.Glob(filepath.Join(fa.GlobDir, "**/*"), nil, pathtools.DontFollowSymlinks)
+			globbed, _, err := z.fs.Glob(filepath.Join(fa.GlobDir, "**/*"), nil, followSymlinks)
 			if err != nil {
 				return err
 			}
@@ -559,7 +567,15 @@
 	var fileSize int64
 	var executable bool
 
-	if s, err := z.fs.Lstat(src); err != nil {
+	var s os.FileInfo
+	var err error
+	if z.followSymlinks {
+		s, err = z.fs.Stat(src)
+	} else {
+		s, err = z.fs.Lstat(src)
+	}
+
+	if err != nil {
 		return err
 	} else if s.IsDir() {
 		if z.directories {
diff --git a/zip/zip_test.go b/zip/zip_test.go
index e77801b..a08fb12 100644
--- a/zip/zip_test.go
+++ b/zip/zip_test.go
@@ -105,6 +105,7 @@
 		nonDeflatedFiles map[string]bool
 		dirEntries       bool
 		manifest         string
+		storeSymlinks    bool
 
 		files []zip.FileHeader
 		err   error
@@ -135,6 +136,7 @@
 				SourcePrefixToStrip("a").
 				File("a/**/*"),
 			compressionLevel: 9,
+			storeSymlinks:    true,
 
 			files: []zip.FileHeader{
 				fh("a/a", fileA, zip.Deflate),
@@ -149,6 +151,7 @@
 				SourcePrefixToStrip("a").
 				Dir("a"),
 			compressionLevel: 9,
+			storeSymlinks:    true,
 
 			files: []zip.FileHeader{
 				fh("a/a", fileA, zip.Deflate),
@@ -179,6 +182,7 @@
 				File("a/a/c").
 				File("a/a/d"),
 			compressionLevel: 9,
+			storeSymlinks:    true,
 
 			files: []zip.FileHeader{
 				fh("a/a/a", fileA, zip.Deflate),
@@ -188,6 +192,23 @@
 			},
 		},
 		{
+			name: "follow symlinks",
+			args: fileArgsBuilder().
+				File("a/a/a").
+				File("a/a/b").
+				File("a/a/c").
+				File("a/a/d"),
+			compressionLevel: 9,
+			storeSymlinks:    false,
+
+			files: []zip.FileHeader{
+				fh("a/a/a", fileA, zip.Deflate),
+				fh("a/a/b", fileB, zip.Deflate),
+				fh("a/a/c", fileC, zip.Deflate),
+				fh("a/a/d", fileB, zip.Deflate),
+			},
+		},
+		{
 			name: "list",
 			args: fileArgsBuilder().
 				List("l"),
@@ -359,6 +380,7 @@
 			args.AddDirectoryEntriesToZip = test.dirEntries
 			args.NonDeflatedFiles = test.nonDeflatedFiles
 			args.ManifestSourcePath = test.manifest
+			args.StoreSymlinks = test.storeSymlinks
 			args.Filesystem = mockFs
 
 			buf := &bytes.Buffer{}