1// Copyright 2020 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	"path/filepath"
20	"strings"
21
22	"github.com/google/blueprint"
23	"github.com/google/blueprint/proptools"
24)
25
26// PackagingSpec abstracts a request to place a built artifact at a certain path in a package. A
27// package can be the traditional <partition>.img, but isn't limited to those. Other examples could
28// be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS
29// running on a VM), or a zip archive for some of the host tools.
30type PackagingSpec struct {
31	// Path relative to the root of the package
32	relPathInPackage string
33
34	// The path to the built artifact
35	srcPath Path
36
37	// If this is not empty, then relPathInPackage should be a symlink to this target. (Then
38	// srcPath is of course ignored.)
39	symlinkTarget string
40
41	// Whether relPathInPackage should be marked as executable or not
42	executable bool
43
44	effectiveLicenseFiles *Paths
45
46	partition string
47
48	// Whether this packaging spec represents an installation of the srcPath (i.e. this struct
49	// is created via InstallFile or InstallSymlink) or a simple packaging (i.e. created via
50	// PackageFile).
51	skipInstall bool
52
53	// Paths of aconfig files for the built artifact
54	aconfigPaths *Paths
55
56	// ArchType of the module which produced this packaging spec
57	archType ArchType
58}
59
60func (p *PackagingSpec) Equals(other *PackagingSpec) bool {
61	if other == nil {
62		return false
63	}
64	if p.relPathInPackage != other.relPathInPackage {
65		return false
66	}
67	if p.srcPath != other.srcPath || p.symlinkTarget != other.symlinkTarget {
68		return false
69	}
70	if p.executable != other.executable {
71		return false
72	}
73	if p.partition != other.partition {
74		return false
75	}
76	return true
77}
78
79// Get file name of installed package
80func (p *PackagingSpec) FileName() string {
81	if p.relPathInPackage != "" {
82		return filepath.Base(p.relPathInPackage)
83	}
84
85	return ""
86}
87
88// Path relative to the root of the package
89func (p *PackagingSpec) RelPathInPackage() string {
90	return p.relPathInPackage
91}
92
93func (p *PackagingSpec) SetRelPathInPackage(relPathInPackage string) {
94	p.relPathInPackage = relPathInPackage
95}
96
97func (p *PackagingSpec) EffectiveLicenseFiles() Paths {
98	if p.effectiveLicenseFiles == nil {
99		return Paths{}
100	}
101	return *p.effectiveLicenseFiles
102}
103
104func (p *PackagingSpec) Partition() string {
105	return p.partition
106}
107
108func (p *PackagingSpec) SkipInstall() bool {
109	return p.skipInstall
110}
111
112// Paths of aconfig files for the built artifact
113func (p *PackagingSpec) GetAconfigPaths() Paths {
114	return *p.aconfigPaths
115}
116
117type PackageModule interface {
118	Module
119	packagingBase() *PackagingBase
120
121	// AddDeps adds dependencies to the `deps` modules. This should be called in DepsMutator.
122	// When adding the dependencies, depTag is used as the tag. If `deps` modules are meant to
123	// be copied to a zip in CopyDepsToZip, `depTag` should implement PackagingItem marker interface.
124	AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag)
125
126	// GatherPackagingSpecs gathers PackagingSpecs of transitive dependencies.
127	GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec
128	GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec
129
130	// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
131	// returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
132	// followed by a build rule that unzips it and creates the final output (img, zip, tar.gz,
133	// etc.) from the extracted files
134	CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) []string
135}
136
137// PackagingBase provides basic functionality for packaging dependencies. A module is expected to
138// include this struct and call InitPackageModule.
139type PackagingBase struct {
140	properties PackagingProperties
141
142	// Allows this module to skip missing dependencies. In most cases, this is not required, but
143	// for rare cases like when there's a dependency to a module which exists in certain repo
144	// checkouts, this is needed.
145	IgnoreMissingDependencies bool
146
147	// If this is set to true by a module type inheriting PackagingBase, the deps property
148	// collects the first target only even with compile_multilib: true.
149	DepsCollectFirstTargetOnly bool
150}
151
152type depsProperty struct {
153	// Modules to include in this package
154	Deps proptools.Configurable[[]string] `android:"arch_variant"`
155}
156
157type packagingMultilibProperties struct {
158	First    depsProperty `android:"arch_variant"`
159	Common   depsProperty `android:"arch_variant"`
160	Lib32    depsProperty `android:"arch_variant"`
161	Lib64    depsProperty `android:"arch_variant"`
162	Both     depsProperty `android:"arch_variant"`
163	Prefer32 depsProperty `android:"arch_variant"`
164}
165
166type packagingArchProperties struct {
167	Arm64  depsProperty
168	Arm    depsProperty
169	X86_64 depsProperty
170	X86    depsProperty
171}
172
173type PackagingProperties struct {
174	Deps     proptools.Configurable[[]string] `android:"arch_variant"`
175	Multilib packagingMultilibProperties      `android:"arch_variant"`
176	Arch     packagingArchProperties
177}
178
179func InitPackageModule(p PackageModule) {
180	base := p.packagingBase()
181	p.AddProperties(&base.properties)
182}
183
184func (p *PackagingBase) packagingBase() *PackagingBase {
185	return p
186}
187
188// From deps and multilib.*.deps, select the dependencies that are for the given arch deps is for
189// the current archicture when this module is not configured for multi target. When configured for
190// multi target, deps is selected for each of the targets and is NOT selected for the current
191// architecture which would be Common.
192func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []string {
193	get := func(prop proptools.Configurable[[]string]) []string {
194		return prop.GetOrDefault(ctx, nil)
195	}
196
197	var ret []string
198	if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 {
199		ret = append(ret, get(p.properties.Deps)...)
200	} else if arch.Multilib == "lib32" {
201		ret = append(ret, get(p.properties.Multilib.Lib32.Deps)...)
202		// multilib.prefer32.deps are added for lib32 only when they support 32-bit arch
203		for _, dep := range get(p.properties.Multilib.Prefer32.Deps) {
204			if checkIfOtherModuleSupportsLib32(ctx, dep) {
205				ret = append(ret, dep)
206			}
207		}
208	} else if arch.Multilib == "lib64" {
209		ret = append(ret, get(p.properties.Multilib.Lib64.Deps)...)
210		// multilib.prefer32.deps are added for lib64 only when they don't support 32-bit arch
211		for _, dep := range get(p.properties.Multilib.Prefer32.Deps) {
212			if !checkIfOtherModuleSupportsLib32(ctx, dep) {
213				ret = append(ret, dep)
214			}
215		}
216	} else if arch == Common {
217		ret = append(ret, get(p.properties.Multilib.Common.Deps)...)
218	}
219
220	if p.DepsCollectFirstTargetOnly {
221		if len(get(p.properties.Multilib.First.Deps)) > 0 {
222			ctx.PropertyErrorf("multilib.first.deps", "not supported. use \"deps\" instead")
223		}
224		for i, t := range ctx.MultiTargets() {
225			if t.Arch.ArchType == arch {
226				ret = append(ret, get(p.properties.Multilib.Both.Deps)...)
227				if i == 0 {
228					ret = append(ret, get(p.properties.Deps)...)
229				}
230			}
231		}
232	} else {
233		if len(get(p.properties.Multilib.Both.Deps)) > 0 {
234			ctx.PropertyErrorf("multilib.both.deps", "not supported. use \"deps\" instead")
235		}
236		for i, t := range ctx.MultiTargets() {
237			if t.Arch.ArchType == arch {
238				ret = append(ret, get(p.properties.Deps)...)
239				if i == 0 {
240					ret = append(ret, get(p.properties.Multilib.First.Deps)...)
241				}
242			}
243		}
244	}
245
246	if ctx.Arch().ArchType == Common {
247		switch arch {
248		case Arm64:
249			ret = append(ret, get(p.properties.Arch.Arm64.Deps)...)
250		case Arm:
251			ret = append(ret, get(p.properties.Arch.Arm.Deps)...)
252		case X86_64:
253			ret = append(ret, get(p.properties.Arch.X86_64.Deps)...)
254		case X86:
255			ret = append(ret, get(p.properties.Arch.X86.Deps)...)
256		}
257	}
258
259	return FirstUniqueStrings(ret)
260}
261
262func getSupportedTargets(ctx BaseModuleContext) []Target {
263	var ret []Target
264	// The current and the common OS targets are always supported
265	ret = append(ret, ctx.Target())
266	if ctx.Arch().ArchType != Common {
267		ret = append(ret, Target{Os: ctx.Os(), Arch: Arch{ArchType: Common}})
268	}
269	// If this module is configured for multi targets, those should be supported as well
270	ret = append(ret, ctx.MultiTargets()...)
271	return ret
272}
273
274// getLib32Target returns the 32-bit target from the list of targets this module supports. If this
275// module doesn't support 32-bit target, nil is returned.
276func getLib32Target(ctx BaseModuleContext) *Target {
277	for _, t := range getSupportedTargets(ctx) {
278		if t.Arch.ArchType.Multilib == "lib32" {
279			return &t
280		}
281	}
282	return nil
283}
284
285// checkIfOtherModuleSUpportsLib32 returns true if 32-bit variant of dep exists.
286func checkIfOtherModuleSupportsLib32(ctx BaseModuleContext, dep string) bool {
287	t := getLib32Target(ctx)
288	if t == nil {
289		// This packaging module doesn't support 32bit. No point of checking if dep supports 32-bit
290		// or not.
291		return false
292	}
293	return ctx.OtherModuleFarDependencyVariantExists(t.Variations(), dep)
294}
295
296// PackagingItem is a marker interface for dependency tags.
297// Direct dependencies with a tag implementing PackagingItem are packaged in CopyDepsToZip().
298type PackagingItem interface {
299	// IsPackagingItem returns true if the dep is to be packaged
300	IsPackagingItem() bool
301}
302
303// DepTag provides default implementation of PackagingItem interface.
304// PackagingBase-derived modules can define their own dependency tag by embedding this, which
305// can be passed to AddDeps() or AddDependencies().
306type PackagingItemAlwaysDepTag struct {
307}
308
309// IsPackagingItem returns true if the dep is to be packaged
310func (PackagingItemAlwaysDepTag) IsPackagingItem() bool {
311	return true
312}
313
314// See PackageModule.AddDeps
315func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag) {
316	for _, t := range getSupportedTargets(ctx) {
317		for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) {
318			if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) {
319				continue
320			}
321			ctx.AddFarVariationDependencies(t.Variations(), depTag, dep)
322		}
323	}
324}
325
326func (p *PackagingBase) GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec {
327	m := make(map[string]PackagingSpec)
328
329	var arches []ArchType
330	for _, target := range getSupportedTargets(ctx) {
331		arches = append(arches, target.Arch.ArchType)
332	}
333
334	// filter out packaging specs for unsupported architecture
335	filterArch := func(ps PackagingSpec) bool {
336		for _, arch := range arches {
337			if arch == ps.archType {
338				return true
339			}
340		}
341		return false
342	}
343
344	ctx.VisitDirectDeps(func(child Module) {
345		if pi, ok := ctx.OtherModuleDependencyTag(child).(PackagingItem); !ok || !pi.IsPackagingItem() {
346			return
347		}
348		for _, ps := range child.TransitivePackagingSpecs() {
349			if !filterArch(ps) {
350				continue
351			}
352
353			if filter != nil {
354				if !filter(ps) {
355					continue
356				}
357			}
358			dstPath := ps.relPathInPackage
359			if existingPs, ok := m[dstPath]; ok {
360				if !existingPs.Equals(&ps) {
361					ctx.ModuleErrorf("packaging conflict at %v:\n%v\n%v", dstPath, existingPs, ps)
362				}
363				continue
364			}
365
366			m[dstPath] = ps
367		}
368	})
369	return m
370}
371
372// See PackageModule.GatherPackagingSpecs
373func (p *PackagingBase) GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec {
374	return p.GatherPackagingSpecsWithFilter(ctx, nil)
375}
376
377// CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec
378// entries into the specified directory.
379func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir WritablePath) (entries []string) {
380	if len(specs) == 0 {
381		return entries
382	}
383	seenDir := make(map[string]bool)
384	preparerPath := PathForModuleOut(ctx, "preparer.sh")
385	cmd := builder.Command().Tool(preparerPath)
386	var sb strings.Builder
387	sb.WriteString("set -e\n")
388	for _, k := range SortedKeys(specs) {
389		ps := specs[k]
390		destPath := filepath.Join(dir.String(), ps.relPathInPackage)
391		destDir := filepath.Dir(destPath)
392		entries = append(entries, ps.relPathInPackage)
393		if _, ok := seenDir[destDir]; !ok {
394			seenDir[destDir] = true
395			sb.WriteString(fmt.Sprintf("mkdir -p %s\n", destDir))
396		}
397		if ps.symlinkTarget == "" {
398			cmd.Implicit(ps.srcPath)
399			sb.WriteString(fmt.Sprintf("cp %s %s\n", ps.srcPath, destPath))
400		} else {
401			sb.WriteString(fmt.Sprintf("ln -sf %s %s\n", ps.symlinkTarget, destPath))
402		}
403		if ps.executable {
404			sb.WriteString(fmt.Sprintf("chmod a+x %s\n", destPath))
405		}
406	}
407
408	WriteExecutableFileRuleVerbatim(ctx, preparerPath, sb.String())
409
410	return entries
411}
412
413// See PackageModule.CopyDepsToZip
414func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) (entries []string) {
415	builder := NewRuleBuilder(pctx, ctx)
416
417	dir := PathForModuleOut(ctx, ".zip")
418	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
419	builder.Command().Text("mkdir").Flag("-p").Text(dir.String())
420	entries = p.CopySpecsToDir(ctx, builder, specs, dir)
421
422	builder.Command().
423		BuiltTool("soong_zip").
424		FlagWithOutput("-o ", zipOut).
425		FlagWithArg("-C ", dir.String()).
426		Flag("-L 0"). // no compression because this will be unzipped soon
427		FlagWithArg("-D ", dir.String())
428	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
429
430	builder.Build("zip_deps", fmt.Sprintf("Zipping deps for %s", ctx.ModuleName()))
431	return entries
432}
433