Merge "WriteFileRule: Chunk long content and merge them to result"
diff --git a/android/defs.go b/android/defs.go
index 631dfe8..f5bd362 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"fmt"
 	"strings"
 	"testing"
 
@@ -97,7 +98,7 @@
 	// content to file.
 	writeFile = pctx.AndroidStaticRule("writeFile",
 		blueprint.RuleParams{
-			Command:     `/bin/bash -c 'echo -e "$$0" > $out' $content`,
+			Command:     `/bin/bash -c 'echo -e -n "$$0" > $out' $content`,
 			Description: "writing file $out",
 		},
 		"content")
@@ -133,9 +134,7 @@
 	shellUnescaper = strings.NewReplacer(`'\''`, `'`)
 )
 
-// WriteFileRule creates a ninja rule to write contents to a file.  The contents will be escaped
-// so that the file contains exactly the contents passed to the function, plus a trailing newline.
-func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
+func buildWriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
 	content = echoEscaper.Replace(content)
 	content = proptools.ShellEscape(content)
 	if content == "" {
@@ -151,6 +150,31 @@
 	})
 }
 
+// WriteFileRule creates a ninja rule to write contents to a file.  The contents will be escaped
+// so that the file contains exactly the contents passed to the function, plus a trailing newline.
+func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
+	// This is MAX_ARG_STRLEN subtracted with some safety to account for shell escapes
+	const SHARD_SIZE = 131072 - 10000
+
+	content += "\n"
+	if len(content) > SHARD_SIZE {
+		var chunks WritablePaths
+		for i, c := range ShardString(content, SHARD_SIZE) {
+			tempPath := outputFile.ReplaceExtension(ctx, fmt.Sprintf("%s.%d", outputFile.Ext(), i))
+			buildWriteFileRule(ctx, tempPath, c)
+			chunks = append(chunks, tempPath)
+		}
+		ctx.Build(pctx, BuildParams{
+			Rule:        Cat,
+			Inputs:      chunks.Paths(),
+			Output:      outputFile,
+			Description: "Merging to " + outputFile.Base(),
+		})
+		return
+	}
+	buildWriteFileRule(ctx, outputFile, content)
+}
+
 // shellUnescape reverses proptools.ShellEscape
 func shellUnescape(s string) string {
 	// Remove leading and trailing quotes if present
diff --git a/android/paths.go b/android/paths.go
index b7117a3..5a41cf1 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -138,6 +138,8 @@
 	// the writablePath method doesn't directly do anything,
 	// but it allows a struct to distinguish between whether or not it implements the WritablePath interface
 	writablePath()
+
+	ReplaceExtension(ctx PathContext, ext string) OutputPath
 }
 
 type genPathProvider interface {
@@ -1249,6 +1251,10 @@
 	return p.config.buildDir
 }
 
+func (p InstallPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
+	panic("Not implemented")
+}
+
 var _ Path = InstallPath{}
 var _ WritablePath = InstallPath{}
 
@@ -1511,6 +1517,10 @@
 	return p.config.buildDir
 }
 
+func (p PhonyPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
+	panic("Not implemented")
+}
+
 var _ Path = PhonyPath{}
 var _ WritablePath = PhonyPath{}
 
diff --git a/android/util.go b/android/util.go
index 8e4c0f4..0f940fa 100644
--- a/android/util.go
+++ b/android/util.go
@@ -401,6 +401,23 @@
 	return ret
 }
 
+// ShardString takes a string and returns a slice of strings where the length of each one is
+// at most shardSize.
+func ShardString(s string, shardSize int) []string {
+	if len(s) == 0 {
+		return nil
+	}
+	ret := make([]string, 0, (len(s)+shardSize-1)/shardSize)
+	for len(s) > shardSize {
+		ret = append(ret, s[0:shardSize])
+		s = s[shardSize:]
+	}
+	if len(s) > 0 {
+		ret = append(ret, s)
+	}
+	return ret
+}
+
 // ShardStrings takes a slice of strings, and returns a slice of slices of strings where each one has at most shardSize
 // elements.
 func ShardStrings(s []string, shardSize int) [][]string {