blob: 07ac3e8c05552a07c39dc8b8ae5a6c5791da1635 [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"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/pkg"
versionHistory "go.fuchsia.dev/fuchsia/src/sys/pkg/lib/version-history"
)
// Config contains global build configuration for other build commands
type Config struct {
OutputDir string
ManifestPath string
KeyPath string
TempDir string
PkgName string
PkgVersion string
PkgABIRevision uint64
// 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: "",
PkgVersion: "0",
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 := ioutil.TempDir("", "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",
PkgVersion: "0",
PkgABIRevision: 0,
}
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.PkgVersion, "version", c.PkgVersion, "version of the packages")
fs.Func("api-level", "package API level", func(value string) error {
if c.PkgABIRevision != 0 {
return fmt.Errorf("cannot specify both --api-level and --abi-revision")
}
apiLevel, err := strconv.ParseUint(value, 0, 64)
if err != nil {
return err
}
for _, version := range versionHistory.Versions() {
if version.APILevel == apiLevel {
c.PkgABIRevision = version.ABIRevision
return nil
}
}
return fmt.Errorf("API level %d is not defined in the SDK", apiLevel)
})
fs.Func("abi-revision", "package ABI revision", func(value string) error {
if c.PkgABIRevision != 0 {
return fmt.Errorf("cannot specify both --api-level and --abi-revision")
}
abiRevision, err := strconv.ParseUint(value, 0, 64)
if err != nil {
return err
}
for _, version := range versionHistory.Versions() {
if version.ABIRevision == abiRevision {
c.PkgABIRevision = abiRevision
return nil
}
}
return fmt.Errorf("ABI Revision %d is not defined in the SDK", abiRevision)
})
}
// 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")
}
// MetaFARMerkle returns the path to the meta.far.merkle that build.Seal generates
func (c *Config) MetaFARMerkle() string {
return filepath.Join(c.OutputDir, "meta.far.merkle")
}
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.
{
merkleBytes, err := ioutil.ReadFile(c.MetaFARMerkle())
if err != nil {
return nil, err
}
merkle, err := DecodeMerkleRoot(merkleBytes)
if err != nil {
return nil, err
}
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",
Package: p,
Blobs: blobs,
}, err
}