1*333d2b36SAndroid Build Coastguard Worker// Copyright 2023 Google Inc. All rights reserved. 2*333d2b36SAndroid Build Coastguard Worker// 3*333d2b36SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*333d2b36SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*333d2b36SAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*333d2b36SAndroid Build Coastguard Worker// 7*333d2b36SAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*333d2b36SAndroid Build Coastguard Worker// 9*333d2b36SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*333d2b36SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*333d2b36SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*333d2b36SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*333d2b36SAndroid Build Coastguard Worker// limitations under the License. 14*333d2b36SAndroid Build Coastguard Worker 15*333d2b36SAndroid Build Coastguard Workerpackage android 16*333d2b36SAndroid Build Coastguard Worker 17*333d2b36SAndroid Build Coastguard Workerimport ( 18*333d2b36SAndroid Build Coastguard Worker "crypto/sha1" 19*333d2b36SAndroid Build Coastguard Worker "encoding/hex" 20*333d2b36SAndroid Build Coastguard Worker "fmt" 21*333d2b36SAndroid Build Coastguard Worker "github.com/google/blueprint" 22*333d2b36SAndroid Build Coastguard Worker "io" 23*333d2b36SAndroid Build Coastguard Worker "io/fs" 24*333d2b36SAndroid Build Coastguard Worker "os" 25*333d2b36SAndroid Build Coastguard Worker "path/filepath" 26*333d2b36SAndroid Build Coastguard Worker "strings" 27*333d2b36SAndroid Build Coastguard Worker "testing" 28*333d2b36SAndroid Build Coastguard Worker 29*333d2b36SAndroid Build Coastguard Worker "github.com/google/blueprint/proptools" 30*333d2b36SAndroid Build Coastguard Worker) 31*333d2b36SAndroid Build Coastguard Worker 32*333d2b36SAndroid Build Coastguard Worker// WriteFileRule creates a ninja rule to write contents to a file by immediately writing the 33*333d2b36SAndroid Build Coastguard Worker// contents, plus a trailing newline, to a file in out/soong/raw-${TARGET_PRODUCT}, and then creating 34*333d2b36SAndroid Build Coastguard Worker// a ninja rule to copy the file into place. 35*333d2b36SAndroid Build Coastguard Workerfunc WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) { 36*333d2b36SAndroid Build Coastguard Worker writeFileRule(ctx, outputFile, content, true, false) 37*333d2b36SAndroid Build Coastguard Worker} 38*333d2b36SAndroid Build Coastguard Worker 39*333d2b36SAndroid Build Coastguard Worker// WriteFileRuleVerbatim creates a ninja rule to write contents to a file by immediately writing the 40*333d2b36SAndroid Build Coastguard Worker// contents to a file in out/soong/raw-${TARGET_PRODUCT}, and then creating a ninja rule to copy the file into place. 41*333d2b36SAndroid Build Coastguard Workerfunc WriteFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string) { 42*333d2b36SAndroid Build Coastguard Worker writeFileRule(ctx, outputFile, content, false, false) 43*333d2b36SAndroid Build Coastguard Worker} 44*333d2b36SAndroid Build Coastguard Worker 45*333d2b36SAndroid Build Coastguard Worker// WriteExecutableFileRuleVerbatim is the same as WriteFileRuleVerbatim, but runs chmod +x on the result 46*333d2b36SAndroid Build Coastguard Workerfunc WriteExecutableFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string) { 47*333d2b36SAndroid Build Coastguard Worker writeFileRule(ctx, outputFile, content, false, true) 48*333d2b36SAndroid Build Coastguard Worker} 49*333d2b36SAndroid Build Coastguard Worker 50*333d2b36SAndroid Build Coastguard Worker// tempFile provides a testable wrapper around a file in out/soong/.temp. It writes to a temporary file when 51*333d2b36SAndroid Build Coastguard Worker// not in tests, but writes to a buffer in memory when used in tests. 52*333d2b36SAndroid Build Coastguard Workertype tempFile struct { 53*333d2b36SAndroid Build Coastguard Worker // tempFile contains wraps an io.Writer, which will be file if testMode is false, or testBuf if it is true. 54*333d2b36SAndroid Build Coastguard Worker io.Writer 55*333d2b36SAndroid Build Coastguard Worker 56*333d2b36SAndroid Build Coastguard Worker file *os.File 57*333d2b36SAndroid Build Coastguard Worker testBuf *strings.Builder 58*333d2b36SAndroid Build Coastguard Worker} 59*333d2b36SAndroid Build Coastguard Worker 60*333d2b36SAndroid Build Coastguard Workerfunc newTempFile(ctx BuilderContext, pattern string, testMode bool) *tempFile { 61*333d2b36SAndroid Build Coastguard Worker if testMode { 62*333d2b36SAndroid Build Coastguard Worker testBuf := &strings.Builder{} 63*333d2b36SAndroid Build Coastguard Worker return &tempFile{ 64*333d2b36SAndroid Build Coastguard Worker Writer: testBuf, 65*333d2b36SAndroid Build Coastguard Worker testBuf: testBuf, 66*333d2b36SAndroid Build Coastguard Worker } 67*333d2b36SAndroid Build Coastguard Worker } else { 68*333d2b36SAndroid Build Coastguard Worker f, err := os.CreateTemp(absolutePath(ctx.Config().tempDir()), pattern) 69*333d2b36SAndroid Build Coastguard Worker if err != nil { 70*333d2b36SAndroid Build Coastguard Worker panic(fmt.Errorf("failed to open temporary raw file: %w", err)) 71*333d2b36SAndroid Build Coastguard Worker } 72*333d2b36SAndroid Build Coastguard Worker return &tempFile{ 73*333d2b36SAndroid Build Coastguard Worker Writer: f, 74*333d2b36SAndroid Build Coastguard Worker file: f, 75*333d2b36SAndroid Build Coastguard Worker } 76*333d2b36SAndroid Build Coastguard Worker } 77*333d2b36SAndroid Build Coastguard Worker} 78*333d2b36SAndroid Build Coastguard Worker 79*333d2b36SAndroid Build Coastguard Workerfunc (t *tempFile) close() error { 80*333d2b36SAndroid Build Coastguard Worker if t.file != nil { 81*333d2b36SAndroid Build Coastguard Worker return t.file.Close() 82*333d2b36SAndroid Build Coastguard Worker } 83*333d2b36SAndroid Build Coastguard Worker return nil 84*333d2b36SAndroid Build Coastguard Worker} 85*333d2b36SAndroid Build Coastguard Worker 86*333d2b36SAndroid Build Coastguard Workerfunc (t *tempFile) name() string { 87*333d2b36SAndroid Build Coastguard Worker if t.file != nil { 88*333d2b36SAndroid Build Coastguard Worker return t.file.Name() 89*333d2b36SAndroid Build Coastguard Worker } 90*333d2b36SAndroid Build Coastguard Worker return "temp_file_in_test" 91*333d2b36SAndroid Build Coastguard Worker} 92*333d2b36SAndroid Build Coastguard Worker 93*333d2b36SAndroid Build Coastguard Workerfunc (t *tempFile) rename(to string) { 94*333d2b36SAndroid Build Coastguard Worker if t.file != nil { 95*333d2b36SAndroid Build Coastguard Worker os.MkdirAll(filepath.Dir(to), 0777) 96*333d2b36SAndroid Build Coastguard Worker err := os.Rename(t.file.Name(), to) 97*333d2b36SAndroid Build Coastguard Worker if err != nil { 98*333d2b36SAndroid Build Coastguard Worker panic(fmt.Errorf("failed to rename %s to %s: %w", t.file.Name(), to, err)) 99*333d2b36SAndroid Build Coastguard Worker } 100*333d2b36SAndroid Build Coastguard Worker } 101*333d2b36SAndroid Build Coastguard Worker} 102*333d2b36SAndroid Build Coastguard Worker 103*333d2b36SAndroid Build Coastguard Workerfunc (t *tempFile) remove() error { 104*333d2b36SAndroid Build Coastguard Worker if t.file != nil { 105*333d2b36SAndroid Build Coastguard Worker return os.Remove(t.file.Name()) 106*333d2b36SAndroid Build Coastguard Worker } 107*333d2b36SAndroid Build Coastguard Worker return nil 108*333d2b36SAndroid Build Coastguard Worker} 109*333d2b36SAndroid Build Coastguard Worker 110*333d2b36SAndroid Build Coastguard Workerfunc writeContentToTempFileAndHash(ctx BuilderContext, content string, newline bool) (*tempFile, string) { 111*333d2b36SAndroid Build Coastguard Worker tempFile := newTempFile(ctx, "raw", ctx.Config().captureBuild) 112*333d2b36SAndroid Build Coastguard Worker defer tempFile.close() 113*333d2b36SAndroid Build Coastguard Worker 114*333d2b36SAndroid Build Coastguard Worker hash := sha1.New() 115*333d2b36SAndroid Build Coastguard Worker w := io.MultiWriter(tempFile, hash) 116*333d2b36SAndroid Build Coastguard Worker 117*333d2b36SAndroid Build Coastguard Worker _, err := io.WriteString(w, content) 118*333d2b36SAndroid Build Coastguard Worker if err == nil && newline { 119*333d2b36SAndroid Build Coastguard Worker _, err = io.WriteString(w, "\n") 120*333d2b36SAndroid Build Coastguard Worker } 121*333d2b36SAndroid Build Coastguard Worker if err != nil { 122*333d2b36SAndroid Build Coastguard Worker panic(fmt.Errorf("failed to write to temporary raw file %s: %w", tempFile.name(), err)) 123*333d2b36SAndroid Build Coastguard Worker } 124*333d2b36SAndroid Build Coastguard Worker return tempFile, hex.EncodeToString(hash.Sum(nil)) 125*333d2b36SAndroid Build Coastguard Worker} 126*333d2b36SAndroid Build Coastguard Worker 127*333d2b36SAndroid Build Coastguard Workerfunc writeFileRule(ctx BuilderContext, outputFile WritablePath, content string, newline bool, executable bool) { 128*333d2b36SAndroid Build Coastguard Worker // Write the contents to a temporary file while computing its hash. 129*333d2b36SAndroid Build Coastguard Worker tempFile, hash := writeContentToTempFileAndHash(ctx, content, newline) 130*333d2b36SAndroid Build Coastguard Worker 131*333d2b36SAndroid Build Coastguard Worker // Shard the final location of the raw file into a subdirectory based on the first two characters of the 132*333d2b36SAndroid Build Coastguard Worker // hash to avoid making the raw directory too large and slowing down accesses. 133*333d2b36SAndroid Build Coastguard Worker relPath := filepath.Join(hash[0:2], hash) 134*333d2b36SAndroid Build Coastguard Worker 135*333d2b36SAndroid Build Coastguard Worker // These files are written during soong_build. If something outside the build deleted them there would be no 136*333d2b36SAndroid Build Coastguard Worker // trigger to rerun soong_build, and the build would break with dependencies on missing files. Writing them 137*333d2b36SAndroid Build Coastguard Worker // to their final locations would risk having them deleted when cleaning a module, and would also pollute the 138*333d2b36SAndroid Build Coastguard Worker // output directory with files for modules that have never been built. 139*333d2b36SAndroid Build Coastguard Worker // Instead, the files are written to a separate "raw" directory next to the build.ninja file, and a ninja 140*333d2b36SAndroid Build Coastguard Worker // rule is created to copy the files into their final location as needed. 141*333d2b36SAndroid Build Coastguard Worker // Obsolete files written by previous runs of soong_build must be cleaned up to avoid continually growing 142*333d2b36SAndroid Build Coastguard Worker // disk usage as the hashes of the files change over time. The cleanup must not remove files that were 143*333d2b36SAndroid Build Coastguard Worker // created by previous runs of soong_build for other products, as the build.ninja files for those products 144*333d2b36SAndroid Build Coastguard Worker // may still exist and still reference those files. The raw files from different products are kept 145*333d2b36SAndroid Build Coastguard Worker // separate by appending the Make_suffix to the directory name. 146*333d2b36SAndroid Build Coastguard Worker rawPath := PathForOutput(ctx, "raw"+proptools.String(ctx.Config().productVariables.Make_suffix), relPath) 147*333d2b36SAndroid Build Coastguard Worker 148*333d2b36SAndroid Build Coastguard Worker rawFileInfo := rawFileInfo{ 149*333d2b36SAndroid Build Coastguard Worker relPath: relPath, 150*333d2b36SAndroid Build Coastguard Worker } 151*333d2b36SAndroid Build Coastguard Worker 152*333d2b36SAndroid Build Coastguard Worker if ctx.Config().captureBuild { 153*333d2b36SAndroid Build Coastguard Worker // When running tests tempFile won't write to disk, instead store the contents for later retrieval by 154*333d2b36SAndroid Build Coastguard Worker // ContentFromFileRuleForTests. 155*333d2b36SAndroid Build Coastguard Worker rawFileInfo.contentForTests = tempFile.testBuf.String() 156*333d2b36SAndroid Build Coastguard Worker } 157*333d2b36SAndroid Build Coastguard Worker 158*333d2b36SAndroid Build Coastguard Worker rawFileSet := getRawFileSet(ctx.Config()) 159*333d2b36SAndroid Build Coastguard Worker if _, exists := rawFileSet.LoadOrStore(hash, rawFileInfo); exists { 160*333d2b36SAndroid Build Coastguard Worker // If a raw file with this hash has already been created delete the temporary file. 161*333d2b36SAndroid Build Coastguard Worker tempFile.remove() 162*333d2b36SAndroid Build Coastguard Worker } else { 163*333d2b36SAndroid Build Coastguard Worker // If this is the first time this hash has been seen then move it from the temporary directory 164*333d2b36SAndroid Build Coastguard Worker // to the raw directory. If the file already exists in the raw directory assume it has the correct 165*333d2b36SAndroid Build Coastguard Worker // contents. 166*333d2b36SAndroid Build Coastguard Worker absRawPath := absolutePath(rawPath.String()) 167*333d2b36SAndroid Build Coastguard Worker _, err := os.Stat(absRawPath) 168*333d2b36SAndroid Build Coastguard Worker if os.IsNotExist(err) { 169*333d2b36SAndroid Build Coastguard Worker tempFile.rename(absRawPath) 170*333d2b36SAndroid Build Coastguard Worker } else if err != nil { 171*333d2b36SAndroid Build Coastguard Worker panic(fmt.Errorf("failed to stat %q: %w", absRawPath, err)) 172*333d2b36SAndroid Build Coastguard Worker } else { 173*333d2b36SAndroid Build Coastguard Worker tempFile.remove() 174*333d2b36SAndroid Build Coastguard Worker } 175*333d2b36SAndroid Build Coastguard Worker } 176*333d2b36SAndroid Build Coastguard Worker 177*333d2b36SAndroid Build Coastguard Worker // Emit a rule to copy the file from raw directory to the final requested location in the output tree. 178*333d2b36SAndroid Build Coastguard Worker // Restat is used to ensure that two different products that produce identical files copied from their 179*333d2b36SAndroid Build Coastguard Worker // own raw directories they don't cause everything downstream to rebuild. 180*333d2b36SAndroid Build Coastguard Worker rule := rawFileCopy 181*333d2b36SAndroid Build Coastguard Worker if executable { 182*333d2b36SAndroid Build Coastguard Worker rule = rawFileCopyExecutable 183*333d2b36SAndroid Build Coastguard Worker } 184*333d2b36SAndroid Build Coastguard Worker ctx.Build(pctx, BuildParams{ 185*333d2b36SAndroid Build Coastguard Worker Rule: rule, 186*333d2b36SAndroid Build Coastguard Worker Input: rawPath, 187*333d2b36SAndroid Build Coastguard Worker Output: outputFile, 188*333d2b36SAndroid Build Coastguard Worker Description: "raw " + outputFile.Base(), 189*333d2b36SAndroid Build Coastguard Worker }) 190*333d2b36SAndroid Build Coastguard Worker} 191*333d2b36SAndroid Build Coastguard Worker 192*333d2b36SAndroid Build Coastguard Workervar ( 193*333d2b36SAndroid Build Coastguard Worker rawFileCopy = pctx.AndroidStaticRule("rawFileCopy", 194*333d2b36SAndroid Build Coastguard Worker blueprint.RuleParams{ 195*333d2b36SAndroid Build Coastguard Worker Command: "if ! cmp -s $in $out; then cp $in $out; fi", 196*333d2b36SAndroid Build Coastguard Worker Description: "copy raw file $out", 197*333d2b36SAndroid Build Coastguard Worker Restat: true, 198*333d2b36SAndroid Build Coastguard Worker }) 199*333d2b36SAndroid Build Coastguard Worker rawFileCopyExecutable = pctx.AndroidStaticRule("rawFileCopyExecutable", 200*333d2b36SAndroid Build Coastguard Worker blueprint.RuleParams{ 201*333d2b36SAndroid Build Coastguard Worker Command: "if ! cmp -s $in $out; then cp $in $out; fi && chmod +x $out", 202*333d2b36SAndroid Build Coastguard Worker Description: "copy raw exectuable file $out", 203*333d2b36SAndroid Build Coastguard Worker Restat: true, 204*333d2b36SAndroid Build Coastguard Worker }) 205*333d2b36SAndroid Build Coastguard Worker) 206*333d2b36SAndroid Build Coastguard Worker 207*333d2b36SAndroid Build Coastguard Workertype rawFileInfo struct { 208*333d2b36SAndroid Build Coastguard Worker relPath string 209*333d2b36SAndroid Build Coastguard Worker contentForTests string 210*333d2b36SAndroid Build Coastguard Worker} 211*333d2b36SAndroid Build Coastguard Worker 212*333d2b36SAndroid Build Coastguard Workervar rawFileSetKey OnceKey = NewOnceKey("raw file set") 213*333d2b36SAndroid Build Coastguard Worker 214*333d2b36SAndroid Build Coastguard Workerfunc getRawFileSet(config Config) *SyncMap[string, rawFileInfo] { 215*333d2b36SAndroid Build Coastguard Worker return config.Once(rawFileSetKey, func() any { 216*333d2b36SAndroid Build Coastguard Worker return &SyncMap[string, rawFileInfo]{} 217*333d2b36SAndroid Build Coastguard Worker }).(*SyncMap[string, rawFileInfo]) 218*333d2b36SAndroid Build Coastguard Worker} 219*333d2b36SAndroid Build Coastguard Worker 220*333d2b36SAndroid Build Coastguard Worker// ContentFromFileRuleForTests returns the content that was passed to a WriteFileRule for use 221*333d2b36SAndroid Build Coastguard Worker// in tests. 222*333d2b36SAndroid Build Coastguard Workerfunc ContentFromFileRuleForTests(t *testing.T, ctx *TestContext, params TestingBuildParams) string { 223*333d2b36SAndroid Build Coastguard Worker t.Helper() 224*333d2b36SAndroid Build Coastguard Worker if params.Rule != rawFileCopy && params.Rule != rawFileCopyExecutable { 225*333d2b36SAndroid Build Coastguard Worker t.Errorf("expected params.Rule to be rawFileCopy or rawFileCopyExecutable, was %q", params.Rule) 226*333d2b36SAndroid Build Coastguard Worker return "" 227*333d2b36SAndroid Build Coastguard Worker } 228*333d2b36SAndroid Build Coastguard Worker 229*333d2b36SAndroid Build Coastguard Worker key := filepath.Base(params.Input.String()) 230*333d2b36SAndroid Build Coastguard Worker rawFileSet := getRawFileSet(ctx.Config()) 231*333d2b36SAndroid Build Coastguard Worker rawFileInfo, _ := rawFileSet.Load(key) 232*333d2b36SAndroid Build Coastguard Worker 233*333d2b36SAndroid Build Coastguard Worker return rawFileInfo.contentForTests 234*333d2b36SAndroid Build Coastguard Worker} 235*333d2b36SAndroid Build Coastguard Worker 236*333d2b36SAndroid Build Coastguard Workerfunc rawFilesSingletonFactory() Singleton { 237*333d2b36SAndroid Build Coastguard Worker return &rawFilesSingleton{} 238*333d2b36SAndroid Build Coastguard Worker} 239*333d2b36SAndroid Build Coastguard Worker 240*333d2b36SAndroid Build Coastguard Workertype rawFilesSingleton struct{} 241*333d2b36SAndroid Build Coastguard Worker 242*333d2b36SAndroid Build Coastguard Workerfunc (rawFilesSingleton) GenerateBuildActions(ctx SingletonContext) { 243*333d2b36SAndroid Build Coastguard Worker if ctx.Config().captureBuild { 244*333d2b36SAndroid Build Coastguard Worker // Nothing to do when running in tests, no temporary files were created. 245*333d2b36SAndroid Build Coastguard Worker return 246*333d2b36SAndroid Build Coastguard Worker } 247*333d2b36SAndroid Build Coastguard Worker rawFileSet := getRawFileSet(ctx.Config()) 248*333d2b36SAndroid Build Coastguard Worker rawFilesDir := PathForOutput(ctx, "raw"+proptools.String(ctx.Config().productVariables.Make_suffix)).String() 249*333d2b36SAndroid Build Coastguard Worker absRawFilesDir := absolutePath(rawFilesDir) 250*333d2b36SAndroid Build Coastguard Worker err := filepath.WalkDir(absRawFilesDir, func(path string, d fs.DirEntry, err error) error { 251*333d2b36SAndroid Build Coastguard Worker if err != nil { 252*333d2b36SAndroid Build Coastguard Worker return err 253*333d2b36SAndroid Build Coastguard Worker } 254*333d2b36SAndroid Build Coastguard Worker if d.IsDir() { 255*333d2b36SAndroid Build Coastguard Worker // Ignore obsolete directories for now. 256*333d2b36SAndroid Build Coastguard Worker return nil 257*333d2b36SAndroid Build Coastguard Worker } 258*333d2b36SAndroid Build Coastguard Worker 259*333d2b36SAndroid Build Coastguard Worker // Assume the basename of the file is a hash 260*333d2b36SAndroid Build Coastguard Worker key := filepath.Base(path) 261*333d2b36SAndroid Build Coastguard Worker relPath, err := filepath.Rel(absRawFilesDir, path) 262*333d2b36SAndroid Build Coastguard Worker if err != nil { 263*333d2b36SAndroid Build Coastguard Worker return err 264*333d2b36SAndroid Build Coastguard Worker } 265*333d2b36SAndroid Build Coastguard Worker 266*333d2b36SAndroid Build Coastguard Worker // Check if a file with the same hash was written by this run of soong_build. If the file was not written, 267*333d2b36SAndroid Build Coastguard Worker // or if a file with the same hash was written but to a different path in the raw directory, then delete it. 268*333d2b36SAndroid Build Coastguard Worker // Checking that the path matches allows changing the structure of the raw directory, for example to increase 269*333d2b36SAndroid Build Coastguard Worker // the sharding. 270*333d2b36SAndroid Build Coastguard Worker rawFileInfo, written := rawFileSet.Load(key) 271*333d2b36SAndroid Build Coastguard Worker if !written || rawFileInfo.relPath != relPath { 272*333d2b36SAndroid Build Coastguard Worker os.Remove(path) 273*333d2b36SAndroid Build Coastguard Worker } 274*333d2b36SAndroid Build Coastguard Worker return nil 275*333d2b36SAndroid Build Coastguard Worker }) 276*333d2b36SAndroid Build Coastguard Worker if err != nil { 277*333d2b36SAndroid Build Coastguard Worker panic(fmt.Errorf("failed to clean %q: %w", rawFilesDir, err)) 278*333d2b36SAndroid Build Coastguard Worker } 279*333d2b36SAndroid Build Coastguard Worker} 280