getdeps/config.go (80 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
)
// Config contains the sources which need to be fetched
type Config struct {
// Identification of the repo the build is being run from.
// Either `git describe` or `hg id` output.
BuildID string `json:"build_id"`
// Additional config files to include. Their order matters: subsequent ones
// may override values from previous ones.
Includes []string `json:"includes,omitempty"`
Initramfs *Node `json:"initramfs,omitempty"`
Kernel *Node `json:"kernel,omitempty"`
Coreboot *Node `json:"coreboot,omitempty"`
}
// NewConfig creates a new config object by parsing the specified file,
// without loading the includes. See NewConfigWithIncludes to fully load
// a file with its includes.
func NewConfig(data []byte) (*Config, error) {
var config Config
if err := json.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("failed to unmarshal configuration: %v", err)
}
return &config, nil
}
// NewConfigWithIncludes parses a configuration blob and recursively
// any specified `include` directive.
// Any relative include path is considered to be relative to `basedir`.
// If `basedir` is empty, relative paths will be resolved from the current
// working directory.
//
// Note that the order of the includes is meaningful: the latest includes will
// override the earliest. The inclusion tree is traversed depth-first
// pre-order, so the inner includes have always more priority than the outer
// (top-level) ones. Recursion alert!
// The recursion depth has a hard limit of 512, which should be enough to avoid
// loops.
func NewConfigWithIncludes(data []byte, basedir string) (*Config, error) {
maxDepth, currentDepth := uint(512), uint(0)
return newConfigWithIncludes(data, basedir, maxDepth, currentDepth)
}
func newConfigWithIncludes(data []byte, basedir string, maxDepth, currentDepth uint) (*Config, error) {
currentDepth++
if currentDepth > maxDepth {
return nil, fmt.Errorf("maximum recursion depth of %d reached", maxDepth)
}
topConfig, err := NewConfig(data)
if err != nil {
return nil, err
}
config := &Config{}
for _, include := range topConfig.Includes {
if !filepath.IsAbs(include) {
include = filepath.Join(basedir, include)
}
includeData, err := ioutil.ReadFile(include)
if err != nil {
return nil, fmt.Errorf("failed to read file '%s': %v", include, err)
}
other, err := newConfigWithIncludes(includeData, basedir, maxDepth, currentDepth)
if err != nil {
return nil, err
}
config, err = mergeConfigs(config, other)
if err != nil {
return nil, fmt.Errorf("failed to merge file '%s' into the configuration: %v", include, err)
}
}
config, err = mergeConfigs(config, topConfig)
if err != nil {
return nil, fmt.Errorf("failed to merge top config into the configuration: %v", err)
}
return config, nil
}
func mergeConfigs(config1 *Config, config2 *Config) (*Config, error) {
var (
newConfig Config
err error
)
if config1 == nil || config2 == nil {
return nil, fmt.Errorf("config objects to merge must be non-nil")
}
newConfig.Initramfs, err = mergeNodes(config1.Initramfs, config2.Initramfs)
if err != nil {
return &newConfig, fmt.Errorf("error merging initramfs config: %w", err)
}
newConfig.Kernel, err = mergeNodes(config1.Kernel, config2.Kernel)
if err != nil {
return &newConfig, fmt.Errorf("error merging kernel config: %w", err)
}
newConfig.Coreboot, err = mergeNodes(config1.Coreboot, config2.Coreboot)
if err != nil {
return &newConfig, fmt.Errorf("error merging coreboot config: %w", err)
}
return &newConfig, nil
}