| // 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 |
| } |