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

Commit 7a64f7e5 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge changes from topic "package"

* changes:
  add android_filesystem
  Introduce PackagingBase
parents 83f738ca 6f0f688c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ bootstrap_go_package {
        "ninja_deps_test.go",
        "onceper_test.go",
        "package_test.go",
        "packaging_test.go",
        "path_properties_test.go",
        "paths_test.go",
        "prebuilt_test.go",
+171 −0
Original line number Diff line number Diff line
@@ -14,6 +14,13 @@

package android

import (
	"fmt"
	"path/filepath"

	"github.com/google/blueprint"
)

// PackagingSpec abstracts a request to place a built artifact at a certain path in a package.
// A package can be the traditional <partition>.img, but isn't limited to those. Other examples could
// be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS running
@@ -32,3 +39,167 @@ type PackagingSpec struct {
	// Whether relPathInPackage should be marked as executable or not
	executable bool
}

type PackageModule interface {
	Module
	packagingBase() *PackagingBase

	// AddDeps adds dependencies to the `deps` modules. This should be called in DepsMutator.
	AddDeps(ctx BottomUpMutatorContext)

	// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
	// returns zip entries in it.  This is expected to be called in GenerateAndroidBuildActions,
	// followed by a build rule that unzips it and creates the final output (img, zip, tar.gz,
	// etc.) from the extracted files
	CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) []string
}

// PackagingBase provides basic functionality for packaging dependencies. A module is expected to
// include this struct and call InitPackageModule.
type PackagingBase struct {
	properties PackagingProperties

	// Allows this module to skip missing dependencies. In most cases, this
	// is not required, but for rare cases like when there's a dependency
	// to a module which exists in certain repo checkouts, this is needed.
	IgnoreMissingDependencies bool
}

type depsProperty struct {
	// Modules to include in this package
	Deps []string `android:"arch_variant"`
}

type packagingMultilibProperties struct {
	First  depsProperty `android:"arch_variant"`
	Common depsProperty `android:"arch_variant"`
	Lib32  depsProperty `android:"arch_variant"`
	Lib64  depsProperty `android:"arch_variant"`
}

type PackagingProperties struct {
	Deps     []string                    `android:"arch_variant"`
	Multilib packagingMultilibProperties `android:"arch_variant"`
}

type packagingDependencyTag struct{ blueprint.BaseDependencyTag }

var depTag = packagingDependencyTag{}

func InitPackageModule(p PackageModule) {
	base := p.packagingBase()
	p.AddProperties(&base.properties)
}

func (p *PackagingBase) packagingBase() *PackagingBase {
	return p
}

// From deps and multilib.*.deps, select the dependencies that are for the given arch
// deps is for the current archicture when this module is not configured for multi target.
// When configured for multi target, deps is selected for each of the targets and is NOT
// selected for the current architecture which would be Common.
func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []string {
	var ret []string
	if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 {
		ret = append(ret, p.properties.Deps...)
	} else if arch.Multilib == "lib32" {
		ret = append(ret, p.properties.Multilib.Lib32.Deps...)
	} else if arch.Multilib == "lib64" {
		ret = append(ret, p.properties.Multilib.Lib64.Deps...)
	} else if arch == Common {
		ret = append(ret, p.properties.Multilib.Common.Deps...)
	}
	for i, t := range ctx.MultiTargets() {
		if t.Arch.ArchType == arch {
			ret = append(ret, p.properties.Deps...)
			if i == 0 {
				ret = append(ret, p.properties.Multilib.First.Deps...)
			}
		}
	}
	return FirstUniqueStrings(ret)
}

func (p *PackagingBase) getSupportedTargets(ctx BaseModuleContext) []Target {
	var ret []Target
	// The current and the common OS targets are always supported
	ret = append(ret, ctx.Target())
	if ctx.Arch().ArchType != Common {
		ret = append(ret, Target{Os: ctx.Os(), Arch: Arch{ArchType: Common}})
	}
	// If this module is configured for multi targets, those should be supported as well
	ret = append(ret, ctx.MultiTargets()...)
	return ret
}

// See PackageModule.AddDeps
func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext) {
	for _, t := range p.getSupportedTargets(ctx) {
		for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) {
			if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) {
				continue
			}
			ctx.AddFarVariationDependencies(t.Variations(), depTag, dep)
		}
	}
}

// See PackageModule.CopyDepsToZip
func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) (entries []string) {
	var supportedArches []string
	for _, t := range p.getSupportedTargets(ctx) {
		supportedArches = append(supportedArches, t.Arch.ArchType.String())
	}
	m := make(map[string]PackagingSpec)
	ctx.WalkDeps(func(child Module, parent Module) bool {
		// Don't track modules with unsupported arch
		// TODO(jiyong): remove this when aosp/1501613 lands.
		if !InList(child.Target().Arch.ArchType.String(), supportedArches) {
			return false
		}
		for _, ps := range child.PackagingSpecs() {
			if _, ok := m[ps.relPathInPackage]; !ok {
				m[ps.relPathInPackage] = ps
			}
		}
		return true
	})

	builder := NewRuleBuilder()

	dir := PathForModuleOut(ctx, ".zip").OutputPath
	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
	builder.Command().Text("mkdir").Flag("-p").Text(dir.String())

	seenDir := make(map[string]bool)
	for _, k := range SortedStringKeys(m) {
		ps := m[k]
		destPath := dir.Join(ctx, ps.relPathInPackage).String()
		destDir := filepath.Dir(destPath)
		entries = append(entries, ps.relPathInPackage)
		if _, ok := seenDir[destDir]; !ok {
			seenDir[destDir] = true
			builder.Command().Text("mkdir").Flag("-p").Text(destDir)
		}
		if ps.symlinkTarget == "" {
			builder.Command().Text("cp").Input(ps.srcPath).Text(destPath)
		} else {
			builder.Command().Text("ln").Flag("-sf").Text(ps.symlinkTarget).Text(destPath)
		}
		if ps.executable {
			builder.Command().Text("chmod").Flag("a+x").Text(destPath)
		}
	}

	builder.Command().
		BuiltTool(ctx, "soong_zip").
		FlagWithOutput("-o ", zipOut).
		FlagWithArg("-C ", dir.String()).
		Flag("-L 0"). // no compression because this will be unzipped soon
		FlagWithArg("-D ", dir.String())
	builder.Command().Text("rm").Flag("-rf").Text(dir.String())

	builder.Build(pctx, ctx, "zip_deps", fmt.Sprintf("Zipping deps for %s", ctx.ModuleName()))
	return entries
}
+188 −0
Original line number Diff line number Diff line
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package android

import (
	"reflect"
	"testing"
)

// Module to be packaged
type componentTestModule struct {
	ModuleBase
	props struct {
		Deps []string
	}
}

func componentTestModuleFactory() Module {
	m := &componentTestModule{}
	m.AddProperties(&m.props)
	InitAndroidArchModule(m, HostAndDeviceSupported, MultilibBoth)
	return m
}

func (m *componentTestModule) DepsMutator(ctx BottomUpMutatorContext) {
	ctx.AddDependency(ctx.Module(), nil, m.props.Deps...)
}

func (m *componentTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
	builtFile := PathForModuleOut(ctx, m.Name())
	dir := ctx.Target().Arch.ArchType.Multilib
	installDir := PathForModuleInstall(ctx, dir)
	ctx.InstallFile(installDir, m.Name(), builtFile)
}

// Module that itself is a package
type packageTestModule struct {
	ModuleBase
	PackagingBase

	entries []string
}

func packageTestModuleFactory() Module {
	module := &packageTestModule{}
	InitPackageModule(module)
	InitAndroidMultiTargetsArchModule(module, DeviceSupported, MultilibCommon)
	return module
}

func (m *packageTestModule) DepsMutator(ctx BottomUpMutatorContext) {
	m.AddDeps(ctx)
}

func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
	zipFile := PathForModuleOut(ctx, "myzip.zip").OutputPath
	m.entries = m.CopyDepsToZip(ctx, zipFile)
}

func runPackagingTest(t *testing.T, bp string, expected []string) {
	t.Helper()

	config := TestArchConfig(buildDir, nil, bp, nil)

	ctx := NewTestArchContext(config)
	ctx.RegisterModuleType("component", componentTestModuleFactory)
	ctx.RegisterModuleType("package_module", packageTestModuleFactory)
	ctx.Register()

	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
	FailIfErrored(t, errs)
	_, errs = ctx.PrepareBuildActions(config)
	FailIfErrored(t, errs)

	p := ctx.ModuleForTests("package", "android_common").Module().(*packageTestModule)
	actual := p.entries
	actual = SortedUniqueStrings(actual)
	expected = SortedUniqueStrings(expected)
	if !reflect.DeepEqual(actual, expected) {
		t.Errorf("\ngot: %v\nexpected: %v\n", actual, expected)
	}
}

func TestPackagingBase(t *testing.T) {
	runPackagingTest(t,
		`
		component {
			name: "foo",
		}

		package_module {
			name: "package",
			deps: ["foo"],
		}
		`, []string{"lib64/foo"})

	runPackagingTest(t,
		`
		component {
			name: "foo",
			deps: ["bar"],
		}

		component {
			name: "bar",
		}

		package_module {
			name: "package",
			deps: ["foo"],
		}
		`, []string{"lib64/foo", "lib64/bar"})

	runPackagingTest(t,
		`
		component {
			name: "foo",
			deps: ["bar"],
		}

		component {
			name: "bar",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			compile_multilib: "both",
		}
		`, []string{"lib32/foo", "lib32/bar", "lib64/foo", "lib64/bar"})

	runPackagingTest(t,
		`
		component {
			name: "foo",
		}

		component {
			name: "bar",
			compile_multilib: "32",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			multilib: {
				lib32: {
					deps: ["bar"],
				},
			},
			compile_multilib: "both",
		}
		`, []string{"lib32/foo", "lib32/bar", "lib64/foo"})

	runPackagingTest(t,
		`
		component {
			name: "foo",
		}

		component {
			name: "bar",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			multilib: {
				first: {
					deps: ["bar"],
				},
			},
			compile_multilib: "both",
		}
		`, []string{"lib32/foo", "lib64/foo", "lib64/bar"})
}

filesystem/Android.bp

0 → 100644
+15 −0
Original line number Diff line number Diff line
bootstrap_go_package {
    name: "soong-filesystem",
    pkgPath: "android/soong/filesystem",
    deps: [
        "blueprint",
        "soong",
        "soong-android",
    ],
    srcs: [
        "filesystem.go",
    ],
    testSrcs: [
    ],
    pluginFor: ["soong_build"],
}
+76 −0
Original line number Diff line number Diff line
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package filesystem

import (
	"fmt"

	"android/soong/android"
)

func init() {
	android.RegisterModuleType("android_filesystem", filesystemFactory)
}

type filesystem struct {
	android.ModuleBase
	android.PackagingBase
}

func filesystemFactory() android.Module {
	module := &filesystem{}
	android.InitPackageModule(module)
	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
	return module
}

func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) {
	f.AddDeps(ctx)
}

var pctx = android.NewPackageContext("android/soong/filesystem")

func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	zipFile := android.PathForModuleOut(ctx, "temp.zip").OutputPath
	f.CopyDepsToZip(ctx, zipFile)

	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
	builder := android.NewRuleBuilder()
	builder.Command().
		BuiltTool(ctx, "zipsync").
		FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear.
		Input(zipFile)

	mkuserimg := ctx.Config().HostToolPath(ctx, "mkuserimg_mke2fs")
	propFile := android.PathForModuleOut(ctx, "prop").OutputPath
	// TODO(jiyong): support more filesystem types other than ext4
	propsText := fmt.Sprintf(`mount_point=system\n`+
		`fs_type=ext4\n`+
		`use_dynamic_partition_size=true\n`+
		`ext_mkuserimg=%s\n`, mkuserimg.String())
	builder.Command().Text("echo").Flag("-e").Flag(`"` + propsText + `"`).
		Text(">").Output(propFile).
		Implicit(mkuserimg)

	image := android.PathForModuleOut(ctx, "filesystem.img").OutputPath
	builder.Command().BuiltTool(ctx, "build_image").
		Text(rootDir.String()). // input directory
		Input(propFile).
		Output(image).
		Text(rootDir.String()) // directory where to find fs_config_files|dirs

	// rootDir is not deleted. Might be useful for quick inspection.
	builder.Build(pctx, ctx, "build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
}