1// Copyright 2015 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package android 16 17import ( 18 "fmt" 19 "strings" 20 "testing" 21 22 "github.com/google/blueprint" 23 "github.com/google/blueprint/bootstrap" 24 "github.com/google/blueprint/proptools" 25) 26 27var ( 28 pctx = NewPackageContext("android/soong/android") 29 30 cpPreserveSymlinks = pctx.VariableConfigMethod("cpPreserveSymlinks", 31 Config.CpPreserveSymlinksFlags) 32 33 // A phony rule that is not the built-in Ninja phony rule. The built-in 34 // phony rule has special behavior that is sometimes not desired. See the 35 // Ninja docs for more details. 36 Phony = pctx.AndroidStaticRule("Phony", 37 blueprint.RuleParams{ 38 Command: "# phony $out", 39 Description: "phony $out", 40 }) 41 42 // GeneratedFile is a rule for indicating that a given file was generated 43 // while running soong. This allows the file to be cleaned up if it ever 44 // stops being generated by soong. 45 GeneratedFile = pctx.AndroidStaticRule("GeneratedFile", 46 blueprint.RuleParams{ 47 Command: "# generated $out", 48 Description: "generated $out", 49 Generator: true, 50 }) 51 52 // A copy rule. 53 Cp = pctx.AndroidStaticRule("Cp", 54 blueprint.RuleParams{ 55 Command: "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out", 56 Description: "cp $out", 57 }, 58 "cpFlags") 59 60 // A copy rule that only updates the output if it changed. 61 CpIfChanged = pctx.AndroidStaticRule("CpIfChanged", 62 blueprint.RuleParams{ 63 Command: "if ! cmp -s $in $out; then cp $in $out; fi", 64 Description: "cp if changed $out", 65 Restat: true, 66 }, 67 "cpFlags") 68 69 CpExecutable = pctx.AndroidStaticRule("CpExecutable", 70 blueprint.RuleParams{ 71 Command: "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out && chmod +x $out", 72 Description: "cp $out", 73 }, 74 "cpFlags") 75 76 // A timestamp touch rule. 77 Touch = pctx.AndroidStaticRule("Touch", 78 blueprint.RuleParams{ 79 Command: "touch $out", 80 Description: "touch $out", 81 }) 82 83 // A symlink rule. 84 Symlink = pctx.AndroidStaticRule("Symlink", 85 blueprint.RuleParams{ 86 Command: "rm -f $out && ln -f -s $fromPath $out", 87 Description: "symlink $out", 88 SymlinkOutputs: []string{"$out"}, 89 }, 90 "fromPath") 91 92 ErrorRule = pctx.AndroidStaticRule("Error", 93 blueprint.RuleParams{ 94 Command: `echo "$error" && false`, 95 Description: "error building $out", 96 }, 97 "error") 98 99 Cat = pctx.AndroidStaticRule("Cat", 100 blueprint.RuleParams{ 101 Command: "cat $in > $out", 102 Description: "concatenate licenses $out", 103 }) 104 105 // ubuntu 14.04 offcially use dash for /bin/sh, and its builtin echo command 106 // doesn't support -e option. Therefore we force to use /bin/bash when writing out 107 // content to file. 108 writeFile = pctx.AndroidStaticRule("writeFile", 109 blueprint.RuleParams{ 110 Command: `/bin/bash -c 'echo -e -n "$$0" > $out' $content`, 111 Description: "writing file $out", 112 }, 113 "content") 114 115 // Used only when USE_GOMA=true is set, to restrict non-goma jobs to the local parallelism value 116 localPool = blueprint.NewBuiltinPool("local_pool") 117 118 // Used only by RuleBuilder to identify remoteable rules. Does not actually get created in ninja. 119 remotePool = blueprint.NewBuiltinPool("remote_pool") 120 121 // Used for processes that need significant RAM to ensure there are not too many running in parallel. 122 highmemPool = blueprint.NewBuiltinPool("highmem_pool") 123) 124 125func init() { 126 pctx.Import("github.com/google/blueprint/bootstrap") 127 128 pctx.VariableFunc("RBEWrapper", func(ctx PackageVarContext) string { 129 return ctx.Config().RBEWrapper() 130 }) 131} 132 133var ( 134 // echoEscaper escapes a string such that passing it to "echo -e" will produce the input value. 135 echoEscaper = strings.NewReplacer( 136 `\`, `\\`, // First escape existing backslashes so they aren't interpreted by `echo -e`. 137 "\n", `\n`, // Then replace newlines with \n 138 ) 139 140 // echoEscaper reverses echoEscaper. 141 echoUnescaper = strings.NewReplacer( 142 `\n`, "\n", 143 `\\`, `\`, 144 ) 145 146 // shellUnescaper reverses the replacer in proptools.ShellEscape 147 shellUnescaper = strings.NewReplacer(`'\''`, `'`) 148) 149 150func buildWriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) { 151 content = echoEscaper.Replace(content) 152 content = proptools.NinjaEscape(proptools.ShellEscapeIncludingSpaces(content)) 153 if content == "" { 154 content = "''" 155 } 156 ctx.Build(pctx, BuildParams{ 157 Rule: writeFile, 158 Output: outputFile, 159 Description: "write " + outputFile.Base(), 160 Args: map[string]string{ 161 "content": content, 162 }, 163 }) 164} 165 166// WriteFileRule creates a ninja rule to write contents to a file. The contents will be escaped 167// so that the file contains exactly the contents passed to the function, plus a trailing newline. 168func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) { 169 // This is MAX_ARG_STRLEN subtracted with some safety to account for shell escapes 170 const SHARD_SIZE = 131072 - 10000 171 172 content += "\n" 173 if len(content) > SHARD_SIZE { 174 var chunks WritablePaths 175 for i, c := range ShardString(content, SHARD_SIZE) { 176 tempPath := outputFile.ReplaceExtension(ctx, fmt.Sprintf("%s.%d", outputFile.Ext(), i)) 177 buildWriteFileRule(ctx, tempPath, c) 178 chunks = append(chunks, tempPath) 179 } 180 ctx.Build(pctx, BuildParams{ 181 Rule: Cat, 182 Inputs: chunks.Paths(), 183 Output: outputFile, 184 Description: "Merging to " + outputFile.Base(), 185 }) 186 return 187 } 188 buildWriteFileRule(ctx, outputFile, content) 189} 190 191// shellUnescape reverses proptools.ShellEscape 192func shellUnescape(s string) string { 193 // Remove leading and trailing quotes if present 194 if len(s) >= 2 && s[0] == '\'' { 195 s = s[1 : len(s)-1] 196 } 197 s = shellUnescaper.Replace(s) 198 return s 199} 200 201// ContentFromFileRuleForTests returns the content that was passed to a WriteFileRule for use 202// in tests. 203func ContentFromFileRuleForTests(t *testing.T, params TestingBuildParams) string { 204 t.Helper() 205 if g, w := params.Rule, writeFile; g != w { 206 t.Errorf("expected params.Rule to be %q, was %q", w, g) 207 return "" 208 } 209 210 content := params.Args["content"] 211 content = shellUnescape(content) 212 content = echoUnescaper.Replace(content) 213 214 return content 215} 216 217// GlobToListFileRule creates a rule that writes a list of files matching a pattern to a file. 218func GlobToListFileRule(ctx ModuleContext, pattern string, excludes []string, file WritablePath) { 219 bootstrap.GlobFile(ctx.blueprintModuleContext(), pattern, excludes, file.String()) 220} 221