1// Copyright 2021 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
15// Package bloaty implements a singleton that measures binary (e.g. ELF
16// executable, shared library or Rust rlib) section sizes at build time.
17package bloaty
18
19import (
20	"android/soong/android"
21
22	"github.com/google/blueprint"
23)
24
25const bloatyDescriptorExt = ".bloaty.csv"
26const protoFilename = "binary_sizes.pb.gz"
27
28var (
29	fileSizeMeasurerKey blueprint.ProviderKey
30	pctx                = android.NewPackageContext("android/soong/bloaty")
31
32	// bloaty is used to measure a binary section sizes.
33	bloaty = pctx.AndroidStaticRule("bloaty",
34		blueprint.RuleParams{
35			Command:     "${bloaty} -n 0 --csv ${in} > ${out}",
36			CommandDeps: []string{"${bloaty}"},
37		})
38
39	// The bloaty merger script is used to combine the outputs from bloaty
40	// into a single protobuf.
41	bloatyMerger = pctx.AndroidStaticRule("bloatyMerger",
42		blueprint.RuleParams{
43			Command:        "${bloatyMerger} ${out}.lst ${out}",
44			CommandDeps:    []string{"${bloatyMerger}"},
45			Rspfile:        "${out}.lst",
46			RspfileContent: "${in}",
47		})
48)
49
50func init() {
51	pctx.VariableConfigMethod("hostPrebuiltTag", android.Config.PrebuiltOS)
52	pctx.SourcePathVariable("bloaty", "prebuilts/build-tools/${hostPrebuiltTag}/bin/bloaty")
53	pctx.HostBinToolVariable("bloatyMerger", "bloaty_merger")
54	android.RegisterSingletonType("file_metrics", fileSizesSingleton)
55	fileSizeMeasurerKey = blueprint.NewProvider(measuredFiles{})
56}
57
58// measuredFiles contains the paths of the files measured by a module.
59type measuredFiles struct {
60	paths []android.WritablePath
61}
62
63// MeasureSizeForPaths should be called by binary producers to measure the
64// sizes of artifacts. It must only be called once per module; it will panic
65// otherwise.
66func MeasureSizeForPaths(ctx android.ModuleContext, paths ...android.OptionalPath) {
67	mf := measuredFiles{}
68	for _, p := range paths {
69		if !p.Valid() {
70			continue
71		}
72		if p, ok := p.Path().(android.WritablePath); ok {
73			mf.paths = append(mf.paths, p)
74		}
75	}
76	ctx.SetProvider(fileSizeMeasurerKey, mf)
77}
78
79type sizesSingleton struct{}
80
81func fileSizesSingleton() android.Singleton {
82	return &sizesSingleton{}
83}
84
85func (singleton *sizesSingleton) GenerateBuildActions(ctx android.SingletonContext) {
86	var deps android.Paths
87	ctx.VisitAllModules(func(m android.Module) {
88		if !ctx.ModuleHasProvider(m, fileSizeMeasurerKey) {
89			return
90		}
91		filePaths := ctx.ModuleProvider(m, fileSizeMeasurerKey).(measuredFiles)
92		for _, path := range filePaths.paths {
93			filePath := path.(android.ModuleOutPath)
94			sizeFile := filePath.InSameDir(ctx, filePath.Base()+bloatyDescriptorExt)
95			ctx.Build(pctx, android.BuildParams{
96				Rule:        bloaty,
97				Description: "bloaty " + filePath.Rel(),
98				Input:       filePath,
99				Output:      sizeFile,
100			})
101			deps = append(deps, sizeFile)
102		}
103	})
104
105	ctx.Build(pctx, android.BuildParams{
106		Rule:   bloatyMerger,
107		Inputs: android.SortedUniquePaths(deps),
108		Output: android.PathForOutput(ctx, protoFilename),
109	})
110}
111
112func (singleton *sizesSingleton) MakeVars(ctx android.MakeVarsContext) {
113	ctx.DistForGoalWithFilename("checkbuild", android.PathForOutput(ctx, protoFilename), protoFilename)
114}
115