Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fa29764f authored by Yu Liu's avatar Yu Liu
Browse files

Experimental code to support build action caching.

Bug: 335718784
Test: build locally
Change-Id: Icc1f1fb15f9fe305e95dd51e2e7aff1e9cbf340c
parent d6352efd
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import (
type DeclarationsModule struct {
	android.ModuleBase
	android.DefaultableModuleBase
	blueprint.IncrementalModule

	// Properties for "aconfig_declarations"
	properties struct {
@@ -157,3 +158,17 @@ func (module *DeclarationsModule) GenerateAndroidBuildActions(ctx android.Module
		IntermediateDumpOutputPath:  intermediateDumpFilePath,
	})
}

func (module *DeclarationsModule) BuildActionProviderKeys() []blueprint.AnyProviderKey {
	return []blueprint.AnyProviderKey{android.AconfigDeclarationsProviderKey}
}

func (module *DeclarationsModule) PackageContextPath() string {
	return pkgPath
}

func (module *DeclarationsModule) CachedRules() []blueprint.Rule {
	return []blueprint.Rule{aconfigRule, aconfigTextRule}
}

var _ blueprint.Incremental = &DeclarationsModule{}
+7 −1
Original line number Diff line number Diff line
@@ -15,13 +15,16 @@
package aconfig

import (
	"encoding/gob"

	"android/soong/android"

	"github.com/google/blueprint"
)

var (
	pctx = android.NewPackageContext("android/soong/aconfig")
	pkgPath = "android/soong/aconfig"
	pctx    = android.NewPackageContext(pkgPath)

	// For aconfig_declarations: Generate cache file
	aconfigRule = pctx.AndroidStaticRule("aconfig",
@@ -106,6 +109,9 @@ func init() {
	RegisterBuildComponents(android.InitRegistrationContext)
	pctx.HostBinToolVariable("aconfig", "aconfig")
	pctx.HostBinToolVariable("soong_zip", "soong_zip")

	gob.Register(android.AconfigDeclarationsProviderData{})
	gob.Register(android.ModuleOutPath{})
}

func RegisterBuildComponents(ctx android.RegistrationContext) {
+48 −3
Original line number Diff line number Diff line
@@ -1913,10 +1913,55 @@ func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext)
			return
		}

		incrementalAnalysis := false
		incrementalEnabled := false
		var cacheKey *blueprint.BuildActionCacheKey = nil
		var incrementalModule *blueprint.Incremental = nil
		if ctx.bp.GetIncrementalEnabled() {
			if im, ok := m.module.(blueprint.Incremental); ok {
				incrementalModule = &im
				incrementalEnabled = im.IncrementalSupported()
				incrementalAnalysis = ctx.bp.GetIncrementalAnalysis() && incrementalEnabled
			}
		}
		if incrementalEnabled {
			hash, err := proptools.CalculateHash(m.GetProperties())
			if err != nil {
				ctx.ModuleErrorf("failed to calculate properties hash: %s", err)
				return
			}
			cacheInput := new(blueprint.BuildActionCacheInput)
			cacheInput.PropertiesHash = hash
			ctx.VisitDirectDeps(func(module Module) {
				cacheInput.ProvidersHash =
					append(cacheInput.ProvidersHash, ctx.bp.OtherModuleProviderInitialValueHashes(module))
			})
			hash, err = proptools.CalculateHash(&cacheInput)
			if err != nil {
				ctx.ModuleErrorf("failed to calculate cache input hash: %s", err)
				return
			}
			cacheKey = &blueprint.BuildActionCacheKey{
				Id:        ctx.bp.ModuleId(),
				InputHash: hash,
			}
		}

		restored := false
		if incrementalAnalysis && cacheKey != nil {
			restored = ctx.bp.RestoreBuildActions(cacheKey, incrementalModule)
		}

		if !restored {
			m.module.GenerateAndroidBuildActions(ctx)
			if ctx.Failed() {
				return
			}
		}

		if incrementalEnabled && cacheKey != nil {
			ctx.bp.CacheBuildActions(cacheKey, incrementalModule)
		}

		// Create the set of tagged dist files after calling GenerateAndroidBuildActions
		// as GenerateTaggedDistFiles() calls OutputFiles(tag) and so relies on the
+47 −0
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@
package android

import (
	"bytes"
	"encoding/gob"
	"errors"
	"fmt"
	"os"
	"path/filepath"
@@ -1068,6 +1071,28 @@ type basePath struct {
	rel  string
}

func (p basePath) GobEncode() ([]byte, error) {
	w := new(bytes.Buffer)
	encoder := gob.NewEncoder(w)
	err := errors.Join(encoder.Encode(p.path), encoder.Encode(p.rel))
	if err != nil {
		return nil, err
	}

	return w.Bytes(), nil
}

func (p *basePath) GobDecode(data []byte) error {
	r := bytes.NewBuffer(data)
	decoder := gob.NewDecoder(r)
	err := errors.Join(decoder.Decode(&p.path), decoder.Decode(&p.rel))
	if err != nil {
		return err
	}

	return nil
}

func (p basePath) Ext() string {
	return filepath.Ext(p.path)
}
@@ -1306,6 +1331,28 @@ type OutputPath struct {
	fullPath string
}

func (p OutputPath) GobEncode() ([]byte, error) {
	w := new(bytes.Buffer)
	encoder := gob.NewEncoder(w)
	err := errors.Join(encoder.Encode(p.basePath), encoder.Encode(p.soongOutDir), encoder.Encode(p.fullPath))
	if err != nil {
		return nil, err
	}

	return w.Bytes(), nil
}

func (p *OutputPath) GobDecode(data []byte) error {
	r := bytes.NewBuffer(data)
	decoder := gob.NewDecoder(r)
	err := errors.Join(decoder.Decode(&p.basePath), decoder.Decode(&p.soongOutDir), decoder.Decode(&p.fullPath))
	if err != nil {
		return err
	}

	return nil
}

func (p OutputPath) withRel(rel string) OutputPath {
	p.basePath = p.basePath.withRel(rel)
	p.fullPath = filepath.Join(p.fullPath, rel)
+83 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
@@ -28,11 +29,11 @@ import (
	"android/soong/android/allowlists"
	"android/soong/bp2build"
	"android/soong/shared"

	"github.com/google/blueprint"
	"github.com/google/blueprint/bootstrap"
	"github.com/google/blueprint/deptools"
	"github.com/google/blueprint/metrics"
	"github.com/google/blueprint/proptools"
	androidProtobuf "google.golang.org/protobuf/android"
)

@@ -49,6 +50,14 @@ var (
	cmdlineArgs android.CmdArgs
)

const configCacheFile = "config.cache"

type ConfigCache struct {
	EnvDepsHash                  uint64
	ProductVariableFileTimestamp int64
	SoongBuildFileTimestamp      int64
}

func init() {
	// Flags that make sense in every mode
	flag.StringVar(&topDir, "top", "", "Top directory of the Android source tree")
@@ -82,6 +91,7 @@ func init() {
	// Flags that probably shouldn't be flags of soong_build, but we haven't found
	// the time to remove them yet
	flag.BoolVar(&cmdlineArgs.RunGoTests, "t", false, "build and run go tests during bootstrap")
	flag.BoolVar(&cmdlineArgs.IncrementalBuildActions, "incremental-build-actions", false, "generate build actions incrementally")

	// Disable deterministic randomization in the protobuf package, so incremental
	// builds with unrelated Soong changes don't trigger large rebuilds (since we
@@ -218,6 +228,60 @@ func writeDepFile(outputFile string, eventHandler *metrics.EventHandler, ninjaDe
	maybeQuit(err, "error writing depfile '%s'", depFile)
}

// Check if there are changes to the environment file, product variable file and
// soong_build binary, in which case no incremental will be performed.
func incrementalValid(config android.Config, configCacheFile string) (*ConfigCache, bool) {
	var newConfigCache ConfigCache
	data, err := os.ReadFile(shared.JoinPath(topDir, usedEnvFile))
	if err != nil {
		// Clean build
		if os.IsNotExist(err) {
			data = []byte{}
		} else {
			maybeQuit(err, "")
		}
	}

	newConfigCache.EnvDepsHash, err = proptools.CalculateHash(data)
	newConfigCache.ProductVariableFileTimestamp = getFileTimestamp(filepath.Join(topDir, cmdlineArgs.SoongVariables))
	newConfigCache.SoongBuildFileTimestamp = getFileTimestamp(filepath.Join(topDir, config.HostToolDir(), "soong_build"))
	//TODO(b/344917959): out/soong/dexpreopt.config might need to be checked as well.

	file, err := os.Open(configCacheFile)
	if err != nil && os.IsNotExist(err) {
		return &newConfigCache, false
	}
	maybeQuit(err, "")
	defer file.Close()

	var configCache ConfigCache
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&configCache)
	maybeQuit(err, "")

	return &newConfigCache, newConfigCache == configCache
}

func getFileTimestamp(file string) int64 {
	stat, err := os.Stat(file)
	if err == nil {
		return stat.ModTime().UnixMilli()
	} else if !os.IsNotExist(err) {
		maybeQuit(err, "")
	}
	return 0
}

func writeConfigCache(configCache *ConfigCache, configCacheFile string) {
	file, err := os.Create(configCacheFile)
	maybeQuit(err, "")
	defer file.Close()

	encoder := json.NewEncoder(file)
	err = encoder.Encode(*configCache)
	maybeQuit(err, "")
}

// runSoongOnlyBuild runs the standard Soong build in a number of different modes.
func runSoongOnlyBuild(ctx *android.Context, extraNinjaDeps []string) string {
	ctx.EventHandler.Begin("soong_build")
@@ -319,8 +383,26 @@ func main() {
	ctx := newContext(configuration)
	android.StartBackgroundMetrics(configuration)

	var configCache *ConfigCache
	configFile := filepath.Join(topDir, ctx.Config().OutDir(), configCacheFile)
	incremental := false
	ctx.SetIncrementalEnabled(cmdlineArgs.IncrementalBuildActions)
	if cmdlineArgs.IncrementalBuildActions {
		configCache, incremental = incrementalValid(ctx.Config(), configFile)
	}
	ctx.SetIncrementalAnalysis(incremental)

	ctx.Register()
	finalOutputFile := runSoongOnlyBuild(ctx, extraNinjaDeps)

	if ctx.GetIncrementalEnabled() {
		data, err := shared.EnvFileContents(configuration.EnvDeps())
		maybeQuit(err, "")
		configCache.EnvDepsHash, err = proptools.CalculateHash(data)
		maybeQuit(err, "")
		writeConfigCache(configCache, configFile)
	}

	writeMetrics(configuration, ctx.EventHandler, metricsDir)

	writeUsedEnvironmentFile(configuration)
Loading