1// Copyright 2020 The Android Open Source Project
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.
14package cc
15
16// This file contains singletons to capture vendor and recovery snapshot. They consist of prebuilt
17// modules under AOSP so older vendor and recovery can be built with a newer system in a single
18// source tree.
19
20import (
21	"encoding/json"
22	"path/filepath"
23	"sort"
24	"strings"
25
26	"android/soong/android"
27)
28
29var vendorSnapshotSingleton = snapshotSingleton{
30	"vendor",
31	"SOONG_VENDOR_SNAPSHOT_ZIP",
32	android.OptionalPath{},
33	true,
34	vendorSnapshotImageSingleton,
35	false, /* fake */
36}
37
38var vendorFakeSnapshotSingleton = snapshotSingleton{
39	"vendor",
40	"SOONG_VENDOR_FAKE_SNAPSHOT_ZIP",
41	android.OptionalPath{},
42	true,
43	vendorSnapshotImageSingleton,
44	true, /* fake */
45}
46
47var recoverySnapshotSingleton = snapshotSingleton{
48	"recovery",
49	"SOONG_RECOVERY_SNAPSHOT_ZIP",
50	android.OptionalPath{},
51	false,
52	recoverySnapshotImageSingleton,
53	false, /* fake */
54}
55
56func VendorSnapshotSingleton() android.Singleton {
57	return &vendorSnapshotSingleton
58}
59
60func VendorFakeSnapshotSingleton() android.Singleton {
61	return &vendorFakeSnapshotSingleton
62}
63
64func RecoverySnapshotSingleton() android.Singleton {
65	return &recoverySnapshotSingleton
66}
67
68type snapshotSingleton struct {
69	// Name, e.g., "vendor", "recovery", "ramdisk".
70	name string
71
72	// Make variable that points to the snapshot file, e.g.,
73	// "SOONG_RECOVERY_SNAPSHOT_ZIP".
74	makeVar string
75
76	// Path to the snapshot zip file.
77	snapshotZipFile android.OptionalPath
78
79	// Whether the image supports VNDK extension modules.
80	supportsVndkExt bool
81
82	// Implementation of the image interface specific to the image
83	// associated with this snapshot (e.g., specific to the vendor image,
84	// recovery image, etc.).
85	image snapshotImage
86
87	// Whether this singleton is for fake snapshot or not.
88	// Fake snapshot is a snapshot whose prebuilt binaries and headers are empty.
89	// It is much faster to generate, and can be used to inspect dependencies.
90	fake bool
91}
92
93// Determine if a dir under source tree is an SoC-owned proprietary directory based
94// on vendor snapshot configuration
95// Examples: device/, vendor/
96func isVendorProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
97	return VendorSnapshotSingleton().(*snapshotSingleton).image.isProprietaryPath(dir, deviceConfig)
98}
99
100// Determine if a dir under source tree is an SoC-owned proprietary directory based
101// on recovery snapshot configuration
102// Examples: device/, vendor/
103func isRecoveryProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
104	return RecoverySnapshotSingleton().(*snapshotSingleton).image.isProprietaryPath(dir, deviceConfig)
105}
106
107func isVendorProprietaryModule(ctx android.BaseModuleContext) bool {
108	// Any module in a vendor proprietary path is a vendor proprietary
109	// module.
110	if isVendorProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()) {
111		return true
112	}
113
114	// However if the module is not in a vendor proprietary path, it may
115	// still be a vendor proprietary module. This happens for cc modules
116	// that are excluded from the vendor snapshot, and it means that the
117	// vendor has assumed control of the framework-provided module.
118	if c, ok := ctx.Module().(LinkableInterface); ok {
119		if c.ExcludeFromVendorSnapshot() {
120			return true
121		}
122	}
123
124	return false
125}
126
127func isRecoveryProprietaryModule(ctx android.BaseModuleContext) bool {
128
129	// Any module in a recovery proprietary path is a recovery proprietary
130	// module.
131	if isRecoveryProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()) {
132		return true
133	}
134
135	// However if the module is not in a recovery proprietary path, it may
136	// still be a recovery proprietary module. This happens for cc modules
137	// that are excluded from the recovery snapshot, and it means that the
138	// vendor has assumed control of the framework-provided module.
139
140	if c, ok := ctx.Module().(LinkableInterface); ok {
141		if c.ExcludeFromRecoverySnapshot() {
142			return true
143		}
144	}
145
146	return false
147}
148
149// Determines if the module is a candidate for snapshot.
150func isSnapshotAware(cfg android.DeviceConfig, m LinkableInterface, inProprietaryPath bool, apexInfo android.ApexInfo, image snapshotImage) bool {
151	if !m.Enabled() || m.HiddenFromMake() {
152		return false
153	}
154	// When android/prebuilt.go selects between source and prebuilt, it sets
155	// HideFromMake on the other one to avoid duplicate install rules in make.
156	if m.IsHideFromMake() {
157		return false
158	}
159	// skip proprietary modules, but (for the vendor snapshot only)
160	// include all VNDK (static)
161	if inProprietaryPath && (!image.includeVndk() || !m.IsVndk()) {
162		return false
163	}
164	// If the module would be included based on its path, check to see if
165	// the module is marked to be excluded. If so, skip it.
166	if image.excludeFromSnapshot(m) {
167		return false
168	}
169	if m.Target().Os.Class != android.Device {
170		return false
171	}
172	if m.Target().NativeBridge == android.NativeBridgeEnabled {
173		return false
174	}
175	// the module must be installed in target image
176	if !apexInfo.IsForPlatform() || m.IsSnapshotPrebuilt() || !image.inImage(m)() {
177		return false
178	}
179	// skip kernel_headers which always depend on vendor
180	if m.KernelHeadersDecorator() {
181		return false
182	}
183
184	if m.IsLlndk() {
185		return false
186	}
187
188	// Libraries
189	if sanitizable, ok := m.(PlatformSanitizeable); ok && sanitizable.IsSnapshotLibrary() {
190		if sanitizable.SanitizePropDefined() {
191			// scs and hwasan export both sanitized and unsanitized variants for static and header
192			// Always use unsanitized variants of them.
193			for _, t := range []SanitizerType{scs, Hwasan} {
194				if !sanitizable.Shared() && sanitizable.IsSanitizerEnabled(t) {
195					return false
196				}
197			}
198			// cfi also exports both variants. But for static, we capture both.
199			// This is because cfi static libraries can't be linked from non-cfi modules,
200			// and vice versa. This isn't the case for scs and hwasan sanitizers.
201			if !sanitizable.Static() && !sanitizable.Shared() && sanitizable.IsSanitizerEnabled(cfi) {
202				return false
203			}
204		}
205		if sanitizable.Static() {
206			return sanitizable.OutputFile().Valid() && !image.private(m)
207		}
208		if sanitizable.Shared() {
209			if !sanitizable.OutputFile().Valid() {
210				return false
211			}
212			if image.includeVndk() {
213				if !sanitizable.IsVndk() {
214					return true
215				}
216				return sanitizable.IsVndkExt()
217			}
218		}
219		return true
220	}
221
222	// Binaries and Objects
223	if m.Binary() || m.Object() {
224		return m.OutputFile().Valid()
225	}
226
227	return false
228}
229
230// This is to be saved as .json files, which is for development/vendor_snapshot/update.py.
231// These flags become Android.bp snapshot module properties.
232type snapshotJsonFlags struct {
233	ModuleName          string `json:",omitempty"`
234	RelativeInstallPath string `json:",omitempty"`
235
236	// library flags
237	ExportedDirs       []string `json:",omitempty"`
238	ExportedSystemDirs []string `json:",omitempty"`
239	ExportedFlags      []string `json:",omitempty"`
240	Sanitize           string   `json:",omitempty"`
241	SanitizeMinimalDep bool     `json:",omitempty"`
242	SanitizeUbsanDep   bool     `json:",omitempty"`
243
244	// binary flags
245	Symlinks []string `json:",omitempty"`
246
247	// dependencies
248	SharedLibs  []string `json:",omitempty"`
249	RuntimeLibs []string `json:",omitempty"`
250	Required    []string `json:",omitempty"`
251
252	// extra config files
253	InitRc         []string `json:",omitempty"`
254	VintfFragments []string `json:",omitempty"`
255}
256
257func (c *snapshotSingleton) GenerateBuildActions(ctx android.SingletonContext) {
258	if !c.image.shouldGenerateSnapshot(ctx) {
259		return
260	}
261
262	var snapshotOutputs android.Paths
263
264	/*
265		Vendor snapshot zipped artifacts directory structure:
266		{SNAPSHOT_ARCH}/
267			arch-{TARGET_ARCH}-{TARGET_ARCH_VARIANT}/
268				shared/
269					(.so shared libraries)
270				static/
271					(.a static libraries)
272				header/
273					(header only libraries)
274				binary/
275					(executable binaries)
276				object/
277					(.o object files)
278			arch-{TARGET_2ND_ARCH}-{TARGET_2ND_ARCH_VARIANT}/
279				shared/
280					(.so shared libraries)
281				static/
282					(.a static libraries)
283				header/
284					(header only libraries)
285				binary/
286					(executable binaries)
287				object/
288					(.o object files)
289			NOTICE_FILES/
290				(notice files, e.g. libbase.txt)
291			configs/
292				(config files, e.g. init.rc files, vintf_fragments.xml files, etc.)
293			include/
294				(header files of same directory structure with source tree)
295	*/
296
297	snapshotDir := c.name + "-snapshot"
298	if c.fake {
299		// If this is a fake snapshot singleton, place all files under fake/ subdirectory to avoid
300		// collision with real snapshot files
301		snapshotDir = filepath.Join("fake", snapshotDir)
302	}
303	snapshotArchDir := filepath.Join(snapshotDir, ctx.DeviceConfig().DeviceArch())
304
305	includeDir := filepath.Join(snapshotArchDir, "include")
306	configsDir := filepath.Join(snapshotArchDir, "configs")
307	noticeDir := filepath.Join(snapshotArchDir, "NOTICE_FILES")
308
309	installedNotices := make(map[string]bool)
310	installedConfigs := make(map[string]bool)
311
312	var headers android.Paths
313
314	copyFile := func(ctx android.SingletonContext, path android.Path, out string, fake bool) android.OutputPath {
315		if fake {
316			// All prebuilt binaries and headers are installed by copyFile function. This makes a fake
317			// snapshot just touch prebuilts and headers, rather than installing real files.
318			return writeStringToFileRule(ctx, "", out)
319		} else {
320			return copyFileRule(ctx, path, out)
321		}
322	}
323
324	// installSnapshot function copies prebuilt file (.so, .a, or executable) and json flag file.
325	// For executables, init_rc and vintf_fragments files are also copied.
326	installSnapshot := func(m LinkableInterface, fake bool) android.Paths {
327		targetArch := "arch-" + m.Target().Arch.ArchType.String()
328		if m.Target().Arch.ArchVariant != "" {
329			targetArch += "-" + m.Target().Arch.ArchVariant
330		}
331
332		var ret android.Paths
333
334		prop := snapshotJsonFlags{}
335
336		// Common properties among snapshots.
337		prop.ModuleName = ctx.ModuleName(m)
338		if c.supportsVndkExt && m.IsVndkExt() {
339			// vndk exts are installed to /vendor/lib(64)?/vndk(-sp)?
340			if m.IsVndkSp() {
341				prop.RelativeInstallPath = "vndk-sp"
342			} else {
343				prop.RelativeInstallPath = "vndk"
344			}
345		} else {
346			prop.RelativeInstallPath = m.RelativeInstallPath()
347		}
348		prop.RuntimeLibs = m.SnapshotRuntimeLibs()
349		prop.Required = m.RequiredModuleNames()
350		for _, path := range m.InitRc() {
351			prop.InitRc = append(prop.InitRc, filepath.Join("configs", path.Base()))
352		}
353		for _, path := range m.VintfFragments() {
354			prop.VintfFragments = append(prop.VintfFragments, filepath.Join("configs", path.Base()))
355		}
356
357		// install config files. ignores any duplicates.
358		for _, path := range append(m.InitRc(), m.VintfFragments()...) {
359			out := filepath.Join(configsDir, path.Base())
360			if !installedConfigs[out] {
361				installedConfigs[out] = true
362				ret = append(ret, copyFile(ctx, path, out, fake))
363			}
364		}
365
366		var propOut string
367
368		if m.IsSnapshotLibrary() {
369			exporterInfo := ctx.ModuleProvider(m.Module(), FlagExporterInfoProvider).(FlagExporterInfo)
370
371			// library flags
372			prop.ExportedFlags = exporterInfo.Flags
373			for _, dir := range exporterInfo.IncludeDirs {
374				prop.ExportedDirs = append(prop.ExportedDirs, filepath.Join("include", dir.String()))
375			}
376			for _, dir := range exporterInfo.SystemIncludeDirs {
377				prop.ExportedSystemDirs = append(prop.ExportedSystemDirs, filepath.Join("include", dir.String()))
378			}
379
380			// shared libs dependencies aren't meaningful on static or header libs
381			if m.Shared() {
382				prop.SharedLibs = m.SnapshotSharedLibs()
383			}
384			if sanitizable, ok := m.(PlatformSanitizeable); ok {
385				if sanitizable.Static() && sanitizable.SanitizePropDefined() {
386					prop.SanitizeMinimalDep = sanitizable.MinimalRuntimeDep() || sanitizable.MinimalRuntimeNeeded()
387					prop.SanitizeUbsanDep = sanitizable.UbsanRuntimeDep() || sanitizable.UbsanRuntimeNeeded()
388				}
389			}
390
391			var libType string
392			if m.Static() {
393				libType = "static"
394			} else if m.Shared() {
395				libType = "shared"
396			} else {
397				libType = "header"
398			}
399
400			var stem string
401
402			// install .a or .so
403			if libType != "header" {
404				libPath := m.OutputFile().Path()
405				stem = libPath.Base()
406				if sanitizable, ok := m.(PlatformSanitizeable); ok {
407					if sanitizable.Static() && sanitizable.SanitizePropDefined() && sanitizable.IsSanitizerEnabled(cfi) {
408						// both cfi and non-cfi variant for static libraries can exist.
409						// attach .cfi to distinguish between cfi and non-cfi.
410						// e.g. libbase.a -> libbase.cfi.a
411						ext := filepath.Ext(stem)
412						stem = strings.TrimSuffix(stem, ext) + ".cfi" + ext
413						prop.Sanitize = "cfi"
414						prop.ModuleName += ".cfi"
415					}
416				}
417				snapshotLibOut := filepath.Join(snapshotArchDir, targetArch, libType, stem)
418				ret = append(ret, copyFile(ctx, libPath, snapshotLibOut, fake))
419			} else {
420				stem = ctx.ModuleName(m)
421			}
422
423			propOut = filepath.Join(snapshotArchDir, targetArch, libType, stem+".json")
424		} else if m.Binary() {
425			// binary flags
426			prop.Symlinks = m.Symlinks()
427			prop.SharedLibs = m.SnapshotSharedLibs()
428
429			// install bin
430			binPath := m.OutputFile().Path()
431			snapshotBinOut := filepath.Join(snapshotArchDir, targetArch, "binary", binPath.Base())
432			ret = append(ret, copyFile(ctx, binPath, snapshotBinOut, fake))
433			propOut = snapshotBinOut + ".json"
434		} else if m.Object() {
435			// object files aren't installed to the device, so their names can conflict.
436			// Use module name as stem.
437			objPath := m.OutputFile().Path()
438			snapshotObjOut := filepath.Join(snapshotArchDir, targetArch, "object",
439				ctx.ModuleName(m)+filepath.Ext(objPath.Base()))
440			ret = append(ret, copyFile(ctx, objPath, snapshotObjOut, fake))
441			propOut = snapshotObjOut + ".json"
442		} else {
443			ctx.Errorf("unknown module %q in vendor snapshot", m.String())
444			return nil
445		}
446
447		j, err := json.Marshal(prop)
448		if err != nil {
449			ctx.Errorf("json marshal to %q failed: %#v", propOut, err)
450			return nil
451		}
452		ret = append(ret, writeStringToFileRule(ctx, string(j), propOut))
453
454		return ret
455	}
456
457	ctx.VisitAllModules(func(module android.Module) {
458		m, ok := module.(LinkableInterface)
459		if !ok {
460			return
461		}
462
463		moduleDir := ctx.ModuleDir(module)
464		inProprietaryPath := c.image.isProprietaryPath(moduleDir, ctx.DeviceConfig())
465		apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
466
467		if c.image.excludeFromSnapshot(m) {
468			if inProprietaryPath {
469				// Error: exclude_from_vendor_snapshot applies
470				// to framework-path modules only.
471				ctx.Errorf("module %q in vendor proprietary path %q may not use \"exclude_from_vendor_snapshot: true\"", m.String(), moduleDir)
472				return
473			}
474		}
475
476		if !isSnapshotAware(ctx.DeviceConfig(), m, inProprietaryPath, apexInfo, c.image) {
477			return
478		}
479
480		// If we are using directed snapshot and a module is not included in the
481		// list, we will still include the module as if it was a fake module.
482		// The reason is that soong needs all the dependencies to be present, even
483		// if they are not using during the build.
484		installAsFake := c.fake
485		if c.image.excludeFromDirectedSnapshot(ctx.DeviceConfig(), m.BaseModuleName()) {
486			installAsFake = true
487		}
488
489		// installSnapshot installs prebuilts and json flag files
490		snapshotOutputs = append(snapshotOutputs, installSnapshot(m, installAsFake)...)
491		// just gather headers and notice files here, because they are to be deduplicated
492		if m.IsSnapshotLibrary() {
493			headers = append(headers, m.SnapshotHeaders()...)
494		}
495
496		if len(m.NoticeFiles()) > 0 {
497			noticeName := ctx.ModuleName(m) + ".txt"
498			noticeOut := filepath.Join(noticeDir, noticeName)
499			// skip already copied notice file
500			if !installedNotices[noticeOut] {
501				installedNotices[noticeOut] = true
502				snapshotOutputs = append(snapshotOutputs, combineNoticesRule(ctx, m.NoticeFiles(), noticeOut))
503			}
504		}
505	})
506
507	// install all headers after removing duplicates
508	for _, header := range android.FirstUniquePaths(headers) {
509		snapshotOutputs = append(snapshotOutputs, copyFile(ctx, header, filepath.Join(includeDir, header.String()), c.fake))
510	}
511
512	// All artifacts are ready. Sort them to normalize ninja and then zip.
513	sort.Slice(snapshotOutputs, func(i, j int) bool {
514		return snapshotOutputs[i].String() < snapshotOutputs[j].String()
515	})
516
517	zipPath := android.PathForOutput(
518		ctx,
519		snapshotDir,
520		c.name+"-"+ctx.Config().DeviceName()+".zip")
521	zipRule := android.NewRuleBuilder(pctx, ctx)
522
523	// filenames in rspfile from FlagWithRspFileInputList might be single-quoted. Remove it with tr
524	snapshotOutputList := android.PathForOutput(
525		ctx,
526		snapshotDir,
527		c.name+"-"+ctx.Config().DeviceName()+"_list")
528	rspFile := snapshotOutputList.ReplaceExtension(ctx, "rsp")
529	zipRule.Command().
530		Text("tr").
531		FlagWithArg("-d ", "\\'").
532		FlagWithRspFileInputList("< ", rspFile, snapshotOutputs).
533		FlagWithOutput("> ", snapshotOutputList)
534
535	zipRule.Temporary(snapshotOutputList)
536
537	zipRule.Command().
538		BuiltTool("soong_zip").
539		FlagWithOutput("-o ", zipPath).
540		FlagWithArg("-C ", android.PathForOutput(ctx, snapshotDir).String()).
541		FlagWithInput("-l ", snapshotOutputList)
542
543	zipRule.Build(zipPath.String(), c.name+" snapshot "+zipPath.String())
544	zipRule.DeleteTemporaryFiles()
545	c.snapshotZipFile = android.OptionalPathForPath(zipPath)
546}
547
548func (c *snapshotSingleton) MakeVars(ctx android.MakeVarsContext) {
549	ctx.Strict(
550		c.makeVar,
551		c.snapshotZipFile.String())
552}
553