blob: 884b684b2c0d409f643049b5b8d6ea05333e5e40 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package build
import (
"flag"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
versionHistory "go.fuchsia.dev/fuchsia/src/lib/versioning/version-history/go"
"go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/pkg"
"go.fuchsia.dev/fuchsia/src/sys/pkg/lib/merkle"
)
// Config contains global build configuration for other build commands
type Config struct {
OutputDir string
ManifestPath string
KeyPath string
TempDir string
PkgName string
PkgRepository string
PkgVersion string
PkgABIRevision uint64
SubpackagesPath string
// the manifest is memoized lazily, on the first call to Manifest()
manifest *Manifest
}
// NewConfig initializes a new configuration with conventional defaults
func NewConfig() *Config {
cfg := &Config{
OutputDir: ".",
ManifestPath: ".",
KeyPath: "",
TempDir: os.TempDir(),
PkgName: "",
PkgRepository: "",
PkgVersion: "0",
SubpackagesPath: "",
PkgABIRevision: 0,
}
return cfg
}
// TestConfig produces a configuration suitable for testing. It creates a
// temporary directory as a parent of the returned config.OutputDir and
// config.TempDir. Callers should remove this directory.
func TestConfig() *Config {
d, err := os.MkdirTemp("", "pm-test")
if err != nil {
panic(err)
}
cfg := &Config{
OutputDir: filepath.Join(d, "output"),
ManifestPath: filepath.Join(d, "manifest"),
KeyPath: filepath.Join(d, "key"),
TempDir: filepath.Join(d, "tmp"),
PkgName: "testpackage",
PkgRepository: "testrepository.com",
PkgVersion: "0",
SubpackagesPath: "",
PkgABIRevision: TestABIRevision,
}
for _, d := range []string{cfg.OutputDir, cfg.TempDir} {
os.MkdirAll(d, os.ModePerm)
}
return cfg
}
// InitFlags adds flags to a flagset for altering Config defaults
func (c *Config) InitFlags(fs *flag.FlagSet) {
fs.StringVar(&c.OutputDir, "o", c.OutputDir, "archive output directory")
fs.StringVar(&c.ManifestPath, "m", c.ManifestPath, "build manifest (or package directory)")
fs.StringVar(&c.KeyPath, "k", c.KeyPath, "deprecated; do not use")
fs.StringVar(&c.TempDir, "t", c.TempDir, "temporary directory")
fs.StringVar(&c.PkgName, "n", c.PkgName, "name of the packages")
fs.StringVar(&c.PkgRepository, "r", c.PkgRepository, "repository of the packages")
fs.StringVar(&c.SubpackagesPath, "subpackages", c.SubpackagesPath, "metafile of subpackages")
fs.Func("api-level", "package API level", func(value string) error {
apiLevel, err := strconv.ParseUint(value, 0, 64)
if err != nil {
return err
}
c.PkgABIRevision, err = versionHistory.History().CheckApiLevelForBuild(apiLevel)
return err
})
}
// Manifest initializes and returns the configured manifest. The manifest may be
// modified during the build process to add/remove files.
func (c *Config) Manifest() (*Manifest, error) {
var err error
if c.manifest == nil {
sources := []string{}
if c.ManifestPath != "" {
sources = append(sources, c.ManifestPath)
}
// Only use outputdir as a source if no manifest was supplied.
if c.ManifestPath == "" && c.OutputDir != "" {
sources = append(sources, c.OutputDir)
}
if len(sources) == 0 {
err = os.ErrNotExist
}
c.manifest, err = NewManifest(sources)
}
return c.manifest, err
}
// MetaFAR returns the path to the meta.far that build.Seal generates
func (c *Config) MetaFAR() string {
return filepath.Join(c.OutputDir, "meta.far")
}
func (c *Config) Package() (pkg.Package, error) {
p := pkg.Package{
Name: c.PkgName,
Version: c.PkgVersion,
}
if p.Name == "" {
p.Name = filepath.Base(c.OutputDir)
if p.Name == "." {
var err error
p.Name, err = filepath.Abs(p.Name)
if err != nil {
return p, fmt.Errorf("build: unable to compute package name from directory: %s", err)
}
p.Name = filepath.Base(p.Name)
}
}
return p, nil
}
func (c *Config) BlobInfo() ([]PackageBlobInfo, error) {
manifest, err := c.Manifest()
if err != nil {
return nil, err
}
var result []PackageBlobInfo
// Include a meta FAR entry first. If blobs.sizes becomes the new root
// blob for a package, targets need to know which unnamed blob is the
// meta FAR.
{
archive, err := os.Open(c.MetaFAR())
if err != nil {
return nil, err
}
var tree merkle.Tree
if _, err := tree.ReadFrom(archive); err != nil {
return nil, err
}
var merkle MerkleRoot
copy(merkle[:], tree.Root()[:32])
info, err := os.Stat(c.MetaFAR())
if err != nil {
return nil, err
}
result = append(result, PackageBlobInfo{
SourcePath: c.MetaFAR(),
Path: "meta/",
Merkle: merkle,
Size: uint64(info.Size()),
})
}
contentsPath := filepath.Join(c.OutputDir, "meta", "contents")
contents, err := LoadMetaContents(contentsPath)
if err != nil {
return nil, err
}
contentsKeys := make([]string, 0, len(contents))
for k := range contents {
contentsKeys = append(contentsKeys, k)
}
sort.Strings(contentsKeys)
for _, path := range contentsKeys {
merkle := contents[path]
info, err := os.Stat(manifest.Paths[path])
if err != nil {
return nil, err
}
result = append(result, PackageBlobInfo{
SourcePath: manifest.Paths[path],
Path: path,
Merkle: merkle,
Size: uint64(info.Size()),
})
}
return result, nil
}
func (c *Config) OutputManifest() (*PackageManifest, error) {
p, err := c.Package()
if err != nil {
return nil, err
}
blobs, err := c.BlobInfo()
if err != nil {
return nil, err
}
return &PackageManifest{
Version: "1",
Repository: c.PkgRepository,
Package: p,
Blobs: blobs,
}, err
}