xref: /aosp_15_r20/build/soong/android/raw_files.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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