mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
Add a homebrew cataloger (#3724)
* Cataloger homebrew (#4) * homebrew cataloger * uptd * fixed test * fixed test * fixed tests * fixed lint * inc schema ver * upt schema * fixed integration test * fixed integration tst * fixed test Signed-off-by: Rez Moss <hi@rezmoss.com> * Update parse_homebrew_test.go Signed-off-by: Rez Moss <hi@rezmoss.com> * Update parse_homebrew_test.go fixed DCO Signed-off-by: Rez Moss <hi@rezmoss.com> Signed-off-by: Rez Moss <hi@rezmoss.com> * Update parse_homebrew_test.go add evd anno to test Signed-off-by: Rez Moss <hi@rezmoss.com> * lint Signed-off-by: Rez Moss <hi@rezmoss.com> * fixed test Signed-off-by: Rez Moss <hi@rezmoss.com> * with PR refactors Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * regenerate json schema Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * regenerate jsonschema Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * refactor homebrew parser + add tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * more resiliant variable extraction Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Rez Moss <hi@rezmoss.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
de88b973f8
commit
12d91f47dc
@ -501,6 +501,13 @@ var commonTestCases = []testCase{
|
||||
"Akismet Anti-spam: Spam Protection": "5.3",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find homebrew",
|
||||
pkgType: pkg.HomebrewPkg,
|
||||
pkgInfo: map[string]string{
|
||||
"afflib": "1.2.3",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find php pear/pecl package",
|
||||
pkgType: pkg.PhpPearPkg,
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
desc "Advanced Forensic Format"
|
||||
homepage "https://github.com/sshock/AFFLIBv3"
|
||||
@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.0.29"
|
||||
JSONSchemaVersion = "16.0.30"
|
||||
)
|
||||
|
||||
@ -17,6 +17,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/githubactions"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/haskell"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/homebrew"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
|
||||
@ -166,6 +167,7 @@ func DefaultPackageTaskFactories() Factories {
|
||||
newSimplePackageTaskFactory(bitnamiSbomCataloger.NewCataloger, "bitnami", pkgcataloging.InstalledTag, pkgcataloging.ImageTag),
|
||||
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
|
||||
newSimplePackageTaskFactory(terraform.NewLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "terraform"),
|
||||
newSimplePackageTaskFactory(homebrew.NewCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "homebrew"),
|
||||
|
||||
// deprecated catalogers ////////////////////////////////////////
|
||||
// these are catalogers that should not be selectable other than specific inclusion via name or "deprecated" tag (to remain backwards compatible)
|
||||
@ -173,6 +175,5 @@ func DefaultPackageTaskFactories() Factories {
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetPortableExecutableCataloger, pkgcataloging.DeprecatedTag), // TODO: remove in syft v2.0
|
||||
newSimplePackageTaskFactory(php.NewPeclCataloger, pkgcataloging.DeprecatedTag), // TODO: remove in syft v2.0
|
||||
newSimplePackageTaskFactory(nix.NewStoreCataloger, pkgcataloging.DeprecatedTag), // TODO: remove in syft v2.0
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
3010
schema/json/schema-16.0.30.json
Normal file
3010
schema/json/schema-16.0.30.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.29/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.30/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@ -1072,6 +1072,20 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"HomebrewFormula": {
|
||||
"properties": {
|
||||
"tap": {
|
||||
"type": "string"
|
||||
},
|
||||
"homepage": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"IDLikes": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
@ -1879,6 +1893,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/HaskellHackageStackLockEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/HomebrewFormula"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/JavaArchive"
|
||||
},
|
||||
|
||||
@ -127,6 +127,7 @@ func Originator(p pkg.Package) (typ string, author string) { //nolint: gocyclo,f
|
||||
// it seems that the vast majority of the time the author is an org, not a person
|
||||
typ = orgType
|
||||
author = metadata.Author
|
||||
|
||||
case pkg.SwiplPackEntry:
|
||||
author = formatPersonOrOrg(metadata.Author, metadata.AuthorEmail)
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
pkg.ErlangRebarLockEntry{},
|
||||
pkg.GolangBinaryBuildinfoEntry{},
|
||||
pkg.GolangModuleEntry{},
|
||||
pkg.HomebrewFormula{},
|
||||
pkg.HackageStackYamlLockEntry{},
|
||||
pkg.HackageStackYamlEntry{},
|
||||
pkg.LinuxKernel{},
|
||||
|
||||
@ -76,6 +76,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from GitHub Actions workflow file or composite action file"
|
||||
case pkg.WordpressPluginPkg:
|
||||
answer = "acquired package info from found wordpress plugin PHP source files"
|
||||
case pkg.HomebrewPkg:
|
||||
answer = "acquired package info from Homebrew formula"
|
||||
case pkg.TerraformPkg:
|
||||
answer = "acquired package info from Terraform dependency lock file"
|
||||
default:
|
||||
|
||||
@ -327,6 +327,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"acquired package info from found wordpress plugin PHP source files",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.HomebrewPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"acquired package info from Homebrew formula",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.TerraformPkg,
|
||||
|
||||
@ -30,6 +30,7 @@ func AllTypes() []any {
|
||||
pkg.GolangModuleEntry{},
|
||||
pkg.HackageStackYamlEntry{},
|
||||
pkg.HackageStackYamlLockEntry{},
|
||||
pkg.HomebrewFormula{},
|
||||
pkg.JavaArchive{},
|
||||
pkg.JavaVMInstallation{},
|
||||
pkg.LinuxKernel{},
|
||||
|
||||
@ -112,6 +112,7 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.RustCargoLockEntry{}, "rust-cargo-lock-entry", "RustCargoPackageMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.RustBinaryAuditEntry{}, "rust-cargo-audit-entry", "RustCargoPackageMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.WordpressPluginEntry{}, "wordpress-plugin-entry", "WordpressMetadata"),
|
||||
jsonNames(pkg.HomebrewFormula{}, "homebrew-formula"),
|
||||
jsonNames(pkg.LuaRocksPackage{}, "luarocks-package"),
|
||||
jsonNames(pkg.TerraformLockProviderEntry{}, "terraform-lock-provider-entry"),
|
||||
jsonNames(pkg.DotnetPackagesLockEntry{}, "dotnet-packages-lock-entry"),
|
||||
|
||||
17
syft/pkg/cataloger/homebrew/cataloger.go
Normal file
17
syft/pkg/cataloger/homebrew/cataloger.go
Normal file
@ -0,0 +1,17 @@
|
||||
package homebrew
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
func NewCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("homebrew-cataloger").
|
||||
WithParserByGlobs(
|
||||
parseHomebrewFormula,
|
||||
// forumulas are located at $(brew --repository)/Cellar
|
||||
"**/Cellar/*/*/.brew/*.rb",
|
||||
// taps are located at $(brew --repository)/Library/Taps
|
||||
"**/Library/Taps/*/*/Formula/*.rb",
|
||||
)
|
||||
}
|
||||
82
syft/pkg/cataloger/homebrew/cataloger_test.go
Normal file
82
syft/pkg/cataloger/homebrew/cataloger_test.go
Normal file
@ -0,0 +1,82 @@
|
||||
package homebrew
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_HomebrewCataloger_Globs(t *testing.T) {
|
||||
fixture := "test-fixtures/install-example"
|
||||
|
||||
expected := []string{
|
||||
"opt/homebrew/Cellar/foo/1.2.3/.brew/foo.rb",
|
||||
"opt/homebrew/Library/Taps/testorg/sometap/Formula/bar.rb",
|
||||
}
|
||||
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, fixture).
|
||||
ExpectsResolverContentQueries(expected).
|
||||
TestCataloger(t, NewCataloger())
|
||||
}
|
||||
|
||||
func Test_HomebrewCataloger(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected []pkg.Package
|
||||
expectedRels []artifact.Relationship
|
||||
}{
|
||||
{
|
||||
name: "go case",
|
||||
path: "test-fixtures/install-example",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "bar",
|
||||
Version: "4.5.6",
|
||||
Type: pkg.HomebrewPkg,
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("opt/homebrew/Library/Taps/testorg/sometap/Formula/bar.rb").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValues("MIT")...),
|
||||
FoundBy: "homebrew-cataloger",
|
||||
PURL: "pkg:brew/bar@4.5.6",
|
||||
Metadata: pkg.HomebrewFormula{
|
||||
Tap: "testorg/sometap",
|
||||
Homepage: "https://example.com/bar",
|
||||
Description: "A test Homebrew formula for bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.2.3",
|
||||
Type: pkg.HomebrewPkg,
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("opt/homebrew/Cellar/foo/1.2.3/.brew/foo.rb").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValues("Apache 2.0")...),
|
||||
FoundBy: "homebrew-cataloger",
|
||||
PURL: "pkg:brew/foo@1.2.3",
|
||||
Metadata: pkg.HomebrewFormula{
|
||||
Homepage: "https://example.com/foo",
|
||||
Description: "A test Homebrew formula for Foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, tt.path).
|
||||
Expects(tt.expected, tt.expectedRels).
|
||||
TestCataloger(t, NewCataloger())
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
44
syft/pkg/cataloger/homebrew/package.go
Normal file
44
syft/pkg/cataloger/homebrew/package.go
Normal file
@ -0,0 +1,44 @@
|
||||
package homebrew
|
||||
|
||||
import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func newHomebrewPackage(pd parsedHomebrewData, formulaLocation file.Location) pkg.Package {
|
||||
var licenses []string
|
||||
if pd.License != "" {
|
||||
licenses = append(licenses, pd.License)
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: pd.Name,
|
||||
Version: pd.Version,
|
||||
Type: pkg.HomebrewPkg,
|
||||
Locations: file.NewLocationSet(formulaLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValues(licenses...)...),
|
||||
FoundBy: "homebrew-cataloger",
|
||||
PURL: packageURL(pd.Name, pd.Version),
|
||||
Metadata: pkg.HomebrewFormula{
|
||||
Tap: pd.Tap,
|
||||
Homepage: pd.Homepage,
|
||||
Description: pd.Desc,
|
||||
},
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(name, version string) string {
|
||||
purl := packageurl.NewPackageURL(
|
||||
"brew",
|
||||
"",
|
||||
name,
|
||||
version,
|
||||
nil,
|
||||
"",
|
||||
)
|
||||
return purl.ToString()
|
||||
}
|
||||
37
syft/pkg/cataloger/homebrew/package_test.go
Normal file
37
syft/pkg/cataloger/homebrew/package_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package homebrew
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_packageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
packageName string
|
||||
packageVersion string
|
||||
expected string
|
||||
}{
|
||||
// preemptive based on https://github.com/package-url/purl-spec/pull/281
|
||||
{
|
||||
name: "standard homebrew package URL",
|
||||
packageName: "foo",
|
||||
packageVersion: "1.2.3",
|
||||
expected: "pkg:brew/foo@1.2.3",
|
||||
},
|
||||
{
|
||||
name: "another example",
|
||||
packageName: "bar",
|
||||
packageVersion: "9.8.7",
|
||||
expected: "pkg:brew/bar@9.8.7",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := packageURL(test.packageName, test.packageVersion)
|
||||
assert.Equal(t, test.expected, actual, "expected package URL to match")
|
||||
})
|
||||
}
|
||||
}
|
||||
149
syft/pkg/cataloger/homebrew/parse_homebrew_formula.go
Normal file
149
syft/pkg/cataloger/homebrew/parse_homebrew_formula.go
Normal file
@ -0,0 +1,149 @@
|
||||
package homebrew
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
type parsedHomebrewData struct {
|
||||
Tap string
|
||||
Name string
|
||||
Version string
|
||||
Desc string
|
||||
Homepage string
|
||||
License string
|
||||
}
|
||||
|
||||
func parseHomebrewFormula(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
pd, err := parseFormulaFile(reader)
|
||||
if err != nil {
|
||||
log.WithFields("path", reader.RealPath).Trace("failed to parse formula")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if pd == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
return []pkg.Package{
|
||||
newHomebrewPackage(
|
||||
*pd,
|
||||
reader.Location,
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func parseFormulaFile(reader file.LocationReadCloser) (*parsedHomebrewData, error) {
|
||||
pd := parsedHomebrewData{}
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.Contains(line, "class ") && strings.Contains(line, " < Formula") {
|
||||
// this is the start of the class declaration, ignore anything before this
|
||||
pd = parsedHomebrewData{}
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case matchesVariable(line, "desc"):
|
||||
pd.Desc = getQuotedValue(line)
|
||||
case matchesVariable(line, "homepage"):
|
||||
pd.Homepage = getQuotedValue(line)
|
||||
case matchesVariable(line, "license"):
|
||||
pd.License = getQuotedValue(line)
|
||||
case matchesVariable(line, "name"):
|
||||
pd.Name = getQuotedValue(line)
|
||||
case matchesVariable(line, "version"):
|
||||
pd.Version = getQuotedValue(line)
|
||||
}
|
||||
}
|
||||
|
||||
pd.Tap = getTapFromPath(reader.RealPath)
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pd.Name != "" && pd.Version != "" {
|
||||
return &pd, nil
|
||||
}
|
||||
|
||||
pd.Name, pd.Version = getNameAndVersionFromPath(reader.RealPath)
|
||||
|
||||
return &pd, nil
|
||||
}
|
||||
|
||||
func matchesVariable(line, name string) bool {
|
||||
// should return true if the line starts with "name<space>" or "name<tab>"
|
||||
return strings.HasPrefix(line, name+" ") || strings.HasPrefix(line, name+"\t")
|
||||
}
|
||||
|
||||
func getNameAndVersionFromPath(p string) (string, string) {
|
||||
if p == "" {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
pathParts := strings.Split(p, "/")
|
||||
|
||||
// extract from a formula path...
|
||||
// e.g. /opt/homebrew/Cellar/foo/1.0.0/.brew/foo.rb
|
||||
var name, ver string
|
||||
for i := len(pathParts) - 1; i >= 0; i-- {
|
||||
if pathParts[i] == ".brew" && i-2 >= 0 {
|
||||
name = pathParts[i-2]
|
||||
ver = pathParts[i-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
// get it from the filename
|
||||
name = strings.TrimSuffix(path.Base(p), ".rb")
|
||||
}
|
||||
|
||||
return name, ver
|
||||
}
|
||||
|
||||
func getTapFromPath(path string) string {
|
||||
// get testorg/sometap from opt/homebrew/Library/Taps/testorg/sometap/Formula/bar.rb
|
||||
// key off of Library/Taps/ as the path just before the org/tap name
|
||||
|
||||
paths := strings.Split(path, "Library/Taps/")
|
||||
if len(paths) < 2 {
|
||||
return ""
|
||||
}
|
||||
|
||||
paths = strings.Split(paths[1], "/")
|
||||
if len(paths) < 2 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(paths[0:2], "/")
|
||||
}
|
||||
|
||||
func getQuotedValue(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
start := strings.Index(s, "\"")
|
||||
if start == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
end := strings.LastIndex(s, "\"")
|
||||
if end == -1 || end <= start {
|
||||
return ""
|
||||
}
|
||||
|
||||
return s[start+1 : end]
|
||||
}
|
||||
303
syft/pkg/cataloger/homebrew/parse_homebrew_formula_test.go
Normal file
303
syft/pkg/cataloger/homebrew/parse_homebrew_formula_test.go
Normal file
@ -0,0 +1,303 @@
|
||||
package homebrew
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_ParseHomebrewPackage(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
expected pkg.Package
|
||||
}{
|
||||
{
|
||||
name: "syft example",
|
||||
fixture: "test-fixtures/formulas/syft/1.23.1/.brew/syft.rb",
|
||||
expected: pkg.Package{
|
||||
Name: "syft",
|
||||
Version: "1.23.1",
|
||||
Type: pkg.HomebrewPkg,
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("test-fixtures/formulas/syft/1.23.1/.brew/syft.rb").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValues("Apache License 2.0")...),
|
||||
FoundBy: "homebrew-cataloger",
|
||||
PURL: "pkg:brew/syft@1.23.1",
|
||||
Metadata: pkg.HomebrewFormula{
|
||||
Homepage: "https://github.com/anchore/syft",
|
||||
Description: "A tool that generates a Software Bill Of Materials (SBOM) from container images and filesystems",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "crazy example",
|
||||
fixture: "test-fixtures/formulas/crazy/1.0.0/.brew/crazy.rb",
|
||||
expected: pkg.Package{
|
||||
Name: "crazy",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.HomebrewPkg,
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("test-fixtures/formulas/crazy/1.0.0/.brew/crazy.rb").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
FoundBy: "homebrew-cataloger",
|
||||
PURL: "pkg:brew/crazy@1.0.0",
|
||||
Metadata: pkg.HomebrewFormula{
|
||||
Homepage: "https://www.example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
pkgtest.TestFileParser(t, test.fixture, parseHomebrewFormula, []pkg.Package{test.expected}, nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTapFromPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "valid path",
|
||||
path: "/opt/homebrew/Library/Taps/testorg/sometap/Formula/bar.rb",
|
||||
expected: "testorg/sometap",
|
||||
},
|
||||
{
|
||||
name: "valid path with different prefix",
|
||||
path: "/usr/local/Library/Taps/otherorg/anothertap/Formula/foo.rb",
|
||||
expected: "otherorg/anothertap",
|
||||
},
|
||||
{
|
||||
name: "missing Library/Taps",
|
||||
path: "/opt/homebrew/Cellar/formula.rb",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "incomplete path after Taps",
|
||||
path: "/opt/homebrew/Library/Taps/testorg",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "empty path",
|
||||
path: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := getTapFromPath(tt.path)
|
||||
if result != tt.expected {
|
||||
t.Errorf("getTapFromPath(%q) = %q, want %q", tt.path, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNameAndVersionFromPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expectedName string
|
||||
expectedVer string
|
||||
}{
|
||||
{
|
||||
name: "formula path",
|
||||
path: "/opt/homebrew/Cellar/foo/1.0.0/.brew/foo.rb",
|
||||
expectedName: "foo",
|
||||
expectedVer: "1.0.0",
|
||||
},
|
||||
{
|
||||
name: "formula path with different version",
|
||||
path: "/opt/homebrew/Cellar/bar/2.3.4/.brew/bar.rb",
|
||||
expectedName: "bar",
|
||||
expectedVer: "2.3.4",
|
||||
},
|
||||
{
|
||||
name: "path without .brew directory",
|
||||
path: "/opt/homebrew/Formula/baz.rb",
|
||||
expectedName: "baz",
|
||||
expectedVer: "",
|
||||
},
|
||||
{
|
||||
name: "path with file extension different than filename",
|
||||
path: "/opt/homebrew/Cellar/qux-tool/5.0.1/.brew/qux.rb",
|
||||
expectedName: "qux-tool",
|
||||
expectedVer: "5.0.1",
|
||||
},
|
||||
{
|
||||
name: "empty path",
|
||||
path: "",
|
||||
expectedName: "",
|
||||
expectedVer: "",
|
||||
},
|
||||
{
|
||||
name: "path with no extension",
|
||||
path: "/opt/homebrew/Formula/quux",
|
||||
expectedName: "quux",
|
||||
expectedVer: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
name, ver := getNameAndVersionFromPath(tt.path)
|
||||
if name != tt.expectedName {
|
||||
t.Errorf("getNameAndVersionFromPath(%q) name = %q, want %q", tt.path, name, tt.expectedName)
|
||||
}
|
||||
if ver != tt.expectedVer {
|
||||
t.Errorf("getNameAndVersionFromPath(%q) version = %q, want %q", tt.path, ver, tt.expectedVer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetQuotedValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "simple quoted string",
|
||||
input: "\"hello\"",
|
||||
expected: "hello",
|
||||
},
|
||||
{
|
||||
name: "quoted string with whitespace outside",
|
||||
input: " \"hello world\" ",
|
||||
expected: "hello world",
|
||||
},
|
||||
{
|
||||
name: "quoted string with content before and after",
|
||||
input: "prefix \"extracted value\" suffix",
|
||||
expected: "extracted value",
|
||||
},
|
||||
{
|
||||
name: "multiple quotes - extract first to last",
|
||||
input: "\"first\" something \"last\"",
|
||||
expected: "first\" something \"last",
|
||||
},
|
||||
{
|
||||
name: "nested quotes",
|
||||
input: "\"outer \"inner\" outer\"",
|
||||
expected: "outer \"inner\" outer",
|
||||
},
|
||||
{
|
||||
name: "empty quoted string",
|
||||
input: "\"\"",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "only opening quote",
|
||||
input: "\"unbalanced",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "only closing quote",
|
||||
input: "unbalanced\"",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "whitespace only",
|
||||
input: " ",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "no quotes",
|
||||
input: "hello world",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := getQuotedValue(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("getQuotedValue(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchesVariable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
variableName string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "matches with space",
|
||||
line: "foo = bar",
|
||||
variableName: "foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "matches with tab",
|
||||
line: "bar\tvalue",
|
||||
variableName: "bar",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "no match - different variable",
|
||||
line: "baz = value",
|
||||
variableName: "foo",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no match - substring",
|
||||
line: "foobar = value",
|
||||
variableName: "foo",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no match - no space or tab",
|
||||
line: "foo=value",
|
||||
variableName: "foo",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no match - empty line",
|
||||
line: "",
|
||||
variableName: "foo",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "matches with space and complex value",
|
||||
line: "complex_var complex value with spaces",
|
||||
variableName: "complex_var",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "case sensitive - different case",
|
||||
line: "FOO = value",
|
||||
variableName: "foo",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := matchesVariable(tt.line, tt.variableName)
|
||||
if result != tt.expected {
|
||||
t.Errorf("matchesVariable(%q, %q) = %v, want %v",
|
||||
tt.line, tt.variableName, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,501 @@
|
||||
# source: https://github.com/syhw/homebrew/blob/174cc1183a7d45e4c87efbc7715ea1016234715c/Library/Contributions/example-formula.rb
|
||||
|
||||
# This is a non-functional example formula to showcase all features and
|
||||
# therefore, it's overly complex and dupes stuff just to comment on it.
|
||||
# You may want to use `brew create` to start your own new formula!
|
||||
# Documentation: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md
|
||||
|
||||
## Naming -- Every Homebrew formula is a class of the type `Formula`.
|
||||
# Ruby classes have to start Upper case and dashes are not allowed.
|
||||
# So we transform: `example-formula.rb` into `ExampleFormula`. Further,
|
||||
# Homebrew does enforce that the name of the file and the class correspond.
|
||||
# Check with `brew search` that the name is free.
|
||||
# name = "FALSE POSITIVE comment"
|
||||
# version = "ALSO FALSE POSITIVE comment"
|
||||
|
||||
name = "FALSE POSITIVE global"
|
||||
version = "ALSO FALSE POSITIVE global"
|
||||
|
||||
class ExampleFormula < Formula
|
||||
homepage "https://www.example.com" # used by `brew home example-formula`.
|
||||
revision 1 # This is used when there's no new version but it needs recompiling for another reason.
|
||||
# 0 is default & unwritten.
|
||||
|
||||
# The url of the archive. Prefer https (security and proxy issues):
|
||||
url "https://packed.sources.and.we.prefer.https.example.com/archive-1.2.3.tar.bz2"
|
||||
mirror "https://in.case.the.host.is.down.example.com" # `mirror` is optional.
|
||||
mirror "https://in.case.the.mirror.is.down.example.com" # Mirrors are limitless, but don't go too wild.
|
||||
|
||||
# Optionally specify the download strategy `:using => ...`
|
||||
# `:git`, `:hg`, `:svn`, `:bzr`, `:cvs`,
|
||||
# `:curl` (normal file download. Will also extract.)
|
||||
# `:nounzip` (without extracting)
|
||||
# `:post` (download via an HTTP POST)
|
||||
# `S3DownloadStrategy` (download from S3 using signed request)
|
||||
url "https://some.dont.provide.archives.example.com", :using => :git, :tag => "1.2.3"
|
||||
|
||||
# version is seldom needed, because it's usually autodetected from the URL/tag.
|
||||
# version "1.2-final"
|
||||
|
||||
# For integrity and security, we verify the hash (`openssl dgst -sha1 <FILE>`)
|
||||
# You may also use sha256 if the software uses sha256 on their homepage. Do not use md5.
|
||||
# Either generate the sha locally or leave it empty & `brew install` will tell you the expected.
|
||||
sha1 "cafebabe78901234567890123456789012345678"
|
||||
|
||||
# Stable-only dependencies should be nested inside a `stable` block rather than
|
||||
# using a conditional. It is preferrable to also pull the URL and checksum into
|
||||
# the block if one is necessary.
|
||||
stable do
|
||||
url "https://example.com/foo-1.0.tar.gz"
|
||||
sha1 "cafebabe78901234567890123456789012345678"
|
||||
|
||||
depends_on "libxml2"
|
||||
depends_on "libffi"
|
||||
end
|
||||
|
||||
# Optionally, specify a repository to be used. Brew then generates a
|
||||
# `--HEAD` option. Remember to also test it.
|
||||
# The download strategies (:using =>) are the same as for `url`.
|
||||
# "master" is the default branch and doesn't need stating with a :branch conditional
|
||||
head "https://we.prefer.https.over.git.example.com/.git"
|
||||
head "https://example.com/.git", :branch => "name_of_branch", :revision => "abc123"
|
||||
head "https://hg.is.awesome.but.git.has.won.example.com/", :using => :hg # If autodetect fails.
|
||||
|
||||
head do
|
||||
url "https://example.com/repo.git"
|
||||
|
||||
depends_on "autoconf" => :build
|
||||
depends_on "automake" => :build
|
||||
depends_on "libtool" => :build
|
||||
end
|
||||
|
||||
# The optional devel block is only executed if the user passes `--devel`.
|
||||
# Use this to specify a not-yet-released version of a software.
|
||||
devel do
|
||||
url "https://example.com/archive-2.0-beta.tar.gz"
|
||||
sha1 "1234567890123456789012345678901234567890"
|
||||
|
||||
depends_on "cairo"
|
||||
depends_on "pixman"
|
||||
end
|
||||
|
||||
|
||||
## Options
|
||||
|
||||
# Options can be used as arguments to `brew install`.
|
||||
# To switch features on/off: `"with-something"` or `"with-otherthing"`.
|
||||
# To use another software: `"with-other-software"` or `"without-foo"`
|
||||
# Note, that for dependencies that are `:optional` or `:recommended`, options
|
||||
# are generated automatically.
|
||||
# Build a universal (On newer intel Macs this means a combined 32bit and
|
||||
# 64bit binary/library). LATER: better explain what this means for PPC.
|
||||
option :universal
|
||||
option "with-spam", "The description goes here without a dot at the end"
|
||||
option "with-qt", "Text here overwrites the autogenerated one from `depends_on 'qt'`"
|
||||
|
||||
## Bottles
|
||||
|
||||
# Bottles are pre-built and added by the Homebrew maintainers for you.
|
||||
# If you maintain your own repository, you can add your own bottle links.
|
||||
# https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Bottles.md
|
||||
# You can ignore this block entirely if submitting to Homebrew/Homebrew, It'll be
|
||||
# handled for you by the Brew Test Bot.
|
||||
bottle do
|
||||
root_url "http://mikemcquaid.com" # Optional root to calculate bottle URLs
|
||||
prefix "/opt/homebrew" # Optional HOMEBREW_PREFIX in which the bottles were built.
|
||||
cellar "/opt/homebrew/Cellar" # Optional HOMEBREW_CELLAR in which the bottles were built.
|
||||
revision 1 # Making the old bottle outdated without bumping the version of the formula.
|
||||
sha1 "d3d13fe6f42416765207503a946db01378131d7b" => :yosemite
|
||||
sha1 "cdc48e79de2dee796bb4ba1ad987f6b35ce1c1ee" => :mavericks
|
||||
sha1 "a19b544c8c645d7daad1d39a070a0eb86dfe9b9c" => :mountain_lion
|
||||
end
|
||||
|
||||
def pour_bottle?
|
||||
# Only needed if this formula has to check if using the pre-built
|
||||
# bottle is fine.
|
||||
true
|
||||
end
|
||||
|
||||
## keg_only
|
||||
|
||||
# Software that will not be sym-linked into the `brew --prefix` will only
|
||||
# live in it's Cellar. Other formulae can depend on it and then brew will
|
||||
# add the necessary includes and libs (etc.) during the brewing of that
|
||||
# other formula. But generally, keg_only formulae are not in your PATH
|
||||
# and not seen by compilers if you build your own software outside of
|
||||
# Homebrew. This way, we don't shadow software provided by OS X.
|
||||
keg_only :provided_by_osx
|
||||
keg_only "because I want it so"
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
# The dependencies for this formula. Use strings for the names of other
|
||||
# formulae. Homebrew provides some :special dependencies for stuff that
|
||||
# requires certain extra handling (often changing some ENV vars or
|
||||
# deciding if to use the system provided version or not.)
|
||||
|
||||
# `:build` means this dep is only needed during build.
|
||||
depends_on "cmake" => :build
|
||||
# Explictly name formulae in other taps. Non-optional tap dependencies won't
|
||||
# be accepted in core.
|
||||
depends_on "homebrew/dupes/tcl-tk" => :optional
|
||||
# `:recommended` dependencies are built by default. But a `--without-...`
|
||||
# option is generated to opt-out.
|
||||
depends_on "readline" => :recommended
|
||||
# `:optional` dependencies are NOT built by default but a `--with-...`
|
||||
# options is generated.
|
||||
depends_on "glib" => :optional
|
||||
# If you need to specify that another formula has to be built with/out
|
||||
# certain options (note, no `--` needed before the option):
|
||||
depends_on "zeromq" => "with-pgm"
|
||||
depends_on "qt" => ["with-qtdbus", "developer"] # Multiple options.
|
||||
# Optional and enforce that boost is built with `--with-c++11`.
|
||||
depends_on "boost" => [:optional, "with-c++11"]
|
||||
# If a dependency is only needed in certain cases:
|
||||
depends_on "sqlite" if MacOS.version == :leopard
|
||||
depends_on :xcode # If the formula really needs full Xcode.
|
||||
depends_on :tex # Homebrew does not provide a Tex Distribution.
|
||||
depends_on :fortran # Checks that `gfortran` is available or `FC` is set.
|
||||
depends_on :mpi => :cc # Needs MPI with `cc`
|
||||
depends_on :mpi => [:cc, :cxx, :optional] # Is optional. MPI with `cc` and `cxx`.
|
||||
depends_on :macos => :lion # Needs at least Mac OS X "Lion" aka. 10.7.
|
||||
depends_on :apr # If a formula requires the CLT-provided apr library to exist.
|
||||
depends_on :arch => :intel # If this formula only builds on intel architecture.
|
||||
depends_on :arch => :x86_64 # If this formula only build on intel x86 64bit.
|
||||
depends_on :arch => :ppc # Only builds on PowerPC?
|
||||
depends_on :ld64 # Sometimes ld fails on `MacOS.version < :leopard`. Then use this.
|
||||
depends_on :x11 # X11/XQuartz components. Non-optional X11 deps should go in Homebrew/Homebrew-x11
|
||||
depends_on :osxfuse # Permits the use of the upstream signed binary or our source package.
|
||||
depends_on :tuntap # Does the same thing as above. This is vital for Yosemite and above.
|
||||
depends_on :mysql => :recommended
|
||||
# It is possible to only depend on something if
|
||||
# `build.with?` or `build.without? "another_formula"`:
|
||||
depends_on :mysql # allows brewed or external mysql to be used
|
||||
depends_on :postgresql if build.without? "sqlite"
|
||||
depends_on :hg # Mercurial (external or brewed) is needed
|
||||
|
||||
# If any Python >= 2.7 < 3.x is okay (either from OS X or brewed):
|
||||
depends_on :python
|
||||
# to depend on Python >= 2.7 but use system Python where possible
|
||||
depends_on :python if MacOS.version <= :snow_leopard
|
||||
# Python 3.x if the `--with-python3` is given to `brew install example`
|
||||
depends_on :python3 => :optional
|
||||
|
||||
# Modules/Packages from other languages, such as :chicken, :jruby, :lua,
|
||||
# :node, :ocaml, :perl, :python, :rbx, :ruby, can be specified by
|
||||
depends_on "some_module" => :lua
|
||||
|
||||
## Conflicts
|
||||
|
||||
# If this formula conflicts with another one:
|
||||
conflicts_with "imagemagick", :because => "because this is just a stupid example"
|
||||
|
||||
|
||||
## Failing with a certain compiler?
|
||||
|
||||
# If it is failing for certain compiler:
|
||||
fails_with :llvm do # :llvm is really llvm-gcc
|
||||
build 2334
|
||||
cause "Segmentation fault during linking."
|
||||
end
|
||||
|
||||
fails_with :clang do
|
||||
build 600
|
||||
cause "multiple configure and compile errors"
|
||||
end
|
||||
|
||||
## Resources
|
||||
|
||||
# Additional downloads can be defined as resources and accessed in the
|
||||
# install method. Resources can also be defined inside a stable, devel, or
|
||||
# head block. This mechanism replaces ad-hoc "subformula" classes.
|
||||
resource "additional_files" do
|
||||
url "https://example.com/additional-stuff.tar.gz"
|
||||
sha1 "deadbeef7890123456789012345678901234567890"
|
||||
end
|
||||
|
||||
|
||||
## Patches
|
||||
|
||||
# External patches can be declared using resource-style blocks.
|
||||
patch do
|
||||
url "https://example.com/example_patch.diff"
|
||||
sha1 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||
end
|
||||
|
||||
# A strip level of -p1 is assumed. It can be overridden using a symbol
|
||||
# argument:
|
||||
patch :p0 do
|
||||
url "https://example.com/example_patch.diff"
|
||||
sha1 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||
end
|
||||
|
||||
# Patches can be declared in stable, devel, and head blocks. This form is
|
||||
# preferred over using conditionals.
|
||||
stable do
|
||||
patch do
|
||||
url "https://example.com/example_patch.diff"
|
||||
sha1 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||
end
|
||||
end
|
||||
|
||||
# Embedded (__END__) patches are declared like so:
|
||||
patch :DATA
|
||||
patch :p0, :DATA
|
||||
|
||||
# Patches can also be embedded by passing a string. This makes it possible
|
||||
# to provide multiple embedded patches while making only some of them
|
||||
# conditional.
|
||||
patch :p0, "..."
|
||||
|
||||
## The install method.
|
||||
|
||||
def install
|
||||
# Now the sources (from `url`) are downloaded, hash-checked and
|
||||
# Homebrew has changed into a temporary directory where the
|
||||
# archive has been unpacked or the repository has been cloned.
|
||||
|
||||
# Print a warning (do this rarely)
|
||||
opoo "Dtrace features are experimental!" if build.with? "dtrace"
|
||||
|
||||
# Sometimes we have to change a bit before we install. Mostly we
|
||||
# prefer a patch but if you need the `prefix` of this formula in the
|
||||
# patch you have to resort to `inreplace`, because in the patch
|
||||
# you don't have access to any var defined by the formula. Only
|
||||
# HOMEBREW_PREFIX is available in the embedded patch.
|
||||
# inreplace supports regular expressions.
|
||||
inreplace "somefile.cfg", /look[for]what?/, "replace by #{bin}/tool"
|
||||
|
||||
# To call out to the system, we use the `system` method and we prefer
|
||||
# you give the args separately as in the line below, otherwise a subshell
|
||||
# has to be opened first.
|
||||
system "./bootstrap.sh", "--arg1", "--prefix=#{prefix}"
|
||||
|
||||
# For Cmake, we have some necessary defaults in `std_cmake_args`:
|
||||
system "cmake", ".", *std_cmake_args
|
||||
|
||||
# If the arguments given to configure (or make or cmake) are depending
|
||||
# on options defined above, we usually make a list first and then
|
||||
# use the `args << if <condition>` to append to:
|
||||
args = ["--option1", "--option2"]
|
||||
args << "--i-want-spam" if build.with? "spam"
|
||||
args << "--qt-gui" if build.with? "qt" # "--with-qt" ==> build.with? "qt"
|
||||
args << "--some-new-stuff" if build.head? # if head is used instead of url.
|
||||
args << "--universal-binary" if build.universal?
|
||||
|
||||
# If there are multiple conditional arguments use a block instead of lines.
|
||||
if build.head?
|
||||
args << "--i-want-pizza"
|
||||
args << "--and-a-cold-beer" if build.with? "cold-beer"
|
||||
end
|
||||
|
||||
# If a formula presents a user with a choice, but the choice must be fulfilled:
|
||||
if build.with? "example2"
|
||||
args << "--with-example2"
|
||||
else
|
||||
args << "--with-example1"
|
||||
end
|
||||
|
||||
# The `build.with?` and `build.without?` are smart enough to do the
|
||||
# right thing with respect to defaults defined via `:optional` and
|
||||
# `:recommended` dependencies.
|
||||
|
||||
# If you need to give the path to lib/include of another brewed formula
|
||||
# please use the `opt_prefix` instead of the `prefix` of that other
|
||||
# formula. The reasoning behind this is that `prefix` has the exact
|
||||
# version number and if you update that other formula, things might
|
||||
# break if they remember that exact path. In contrast to that, the
|
||||
# `$(brew --prefix)/opt/formula` is the same path for all future
|
||||
# versions of the formula!
|
||||
args << "--with-readline=#{Formula["readline"].opt_prefix}" if build.with? "readline"
|
||||
|
||||
# Most software still uses `configure` and `make`.
|
||||
# Check with `./configure --help` what our options are.
|
||||
system "./configure", "--disable-debug", "--disable-dependency-tracking",
|
||||
"--disable-silent-rules", "--prefix=#{prefix}",
|
||||
*args # our custom arg list (needs `*` to unpack)
|
||||
|
||||
# If your formula's build system is not thread safe:
|
||||
ENV.deparallelize
|
||||
|
||||
# A general note: The commands here are executed line by line, so if
|
||||
# you change some variable or call a method like ENV.deparallelize, it
|
||||
# only affects the lines after that command.
|
||||
|
||||
# Do something only for clang
|
||||
if ENV.compiler == :clang
|
||||
# modify CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS in one go:
|
||||
ENV.append_to_cflags "-I ./missing/includes"
|
||||
end
|
||||
|
||||
# Overwriting any env var:
|
||||
ENV["LDFLAGS"] = "--tag CC"
|
||||
# Is the formula struggling to find the pkgconfig file? Point it to it.
|
||||
# This is done automatically for `keg_only` formulae.
|
||||
ENV.prepend_path "PKG_CONFIG_PATH", "#{Formula["glib"].opt_lib}/pkgconfig"
|
||||
|
||||
# Need to install into the bin but the makefile doesn't mkdir -p prefix/bin?
|
||||
bin.mkpath
|
||||
# A custom directory?
|
||||
mkdir_p share/"example"
|
||||
# And then move something from the buildpath to that directory?
|
||||
mv "ducks.txt", share/"example/ducks.txt"
|
||||
# No "make", "install" available?
|
||||
bin.install "binary1"
|
||||
include.install "example.h"
|
||||
lib.install "example.dylib"
|
||||
man1.install "example.1"
|
||||
man3.install "example.3"
|
||||
# All that README/LICENSE/NOTES/CHANGELOG stuff? Use "metafiles"
|
||||
prefix.install_metafiles
|
||||
# Maybe you'd like to remove a broken or unnecessary element?
|
||||
# Empty directories will be removed by Homebrew automatically post-install!
|
||||
rm "bin/example"
|
||||
rm_rf "share/pointless"
|
||||
|
||||
# If there is a "make", "install" available, please use it!
|
||||
system "make", "install"
|
||||
|
||||
# We are in a temporary directory and don't have to care about cleanup.
|
||||
|
||||
# Instead of `system "cp"` or something, call `install` on the Pathname
|
||||
# objects as they are smarter with respect to correcting access rights.
|
||||
# (`install` is a Homebrew mixin into Ruby's Pathname)
|
||||
|
||||
# The pathnames defined in the formula
|
||||
prefix # == HOMEBREW_PREFIX+"Cellar"+name+version
|
||||
bin # == prefix+"bin"
|
||||
doc # == share+"doc"+name
|
||||
include # == prefix+"include"
|
||||
info # == share+"info"
|
||||
lib # == prefix+"lib"
|
||||
libexec # == prefix+"libexec"
|
||||
buildpath # The temporary directory where build occurs.
|
||||
|
||||
man # share+"man"
|
||||
man1 # man+"man1"
|
||||
man2 # man+"man2"
|
||||
man3 # man+"man3"
|
||||
man4 # man+"man4"
|
||||
man5 # man+"man5"
|
||||
man6 # man+"man6"
|
||||
man7 # man+"man7"
|
||||
man8 # man+"man8"
|
||||
sbin # prefix+"sbin"
|
||||
share # prefix+"share"
|
||||
frameworks # prefix+"Frameworks"
|
||||
kext_prefix # prefix+"Library/Extensions"
|
||||
# Configuration stuff that will survive formula updates
|
||||
etc # HOMEBREW_PREFIX+"etc"
|
||||
# Generally we don't want var stuff inside the keg
|
||||
var # HOMEBREW_PREFIX+"var"
|
||||
bash_completion # prefix+"etc/bash_completion.d"
|
||||
zsh_completion # share+"zsh/site-functions"
|
||||
# Further possibilities with the pathnames:
|
||||
# http://www.ruby-doc.org/stdlib-1.8.7/libdoc/pathname/rdoc/Pathname.html
|
||||
|
||||
# Copy `./example_code/simple/ones` to share/demos
|
||||
(share/"demos").install "example_code/simple/ones"
|
||||
# Copy `./example_code/simple/ones` to share/demos/examples
|
||||
(share/"demos").install "example_code/simple/ones" => "examples"
|
||||
|
||||
# Additional downloads can be defined as resources (see above).
|
||||
# The stage method will create a temporary directory and yield
|
||||
# to a block.
|
||||
resource("additional_files").stage { bin.install "my/extra/tool" }
|
||||
|
||||
# `name` and `version` are accessible too, if you need them.
|
||||
end
|
||||
|
||||
|
||||
## Caveats
|
||||
|
||||
def caveats
|
||||
"Are optional. Something the user should know?"
|
||||
end
|
||||
|
||||
def caveats
|
||||
s = <<-EOS.undent
|
||||
Print some important notice to the user when `brew info <formula>` is
|
||||
called or when brewing a formula.
|
||||
This is optional. You can use all the vars like #{version} here.
|
||||
EOS
|
||||
s += "Some issue only on older systems" if MacOS.version < :mountain_lion
|
||||
s
|
||||
end
|
||||
|
||||
|
||||
## Test (is optional but makes us happy)
|
||||
|
||||
test do
|
||||
# `test do` will create, run in, and delete a temporary directory.
|
||||
|
||||
# We are fine if the executable does not error out, so we know linking
|
||||
# and building the software was ok.
|
||||
system bin/"foobar", "--version"
|
||||
|
||||
(testpath/"Test.file").write <<-EOS.undent
|
||||
writing some test file, if you need to
|
||||
EOS
|
||||
# To capture the output of a command, we use backtics:
|
||||
assert_equal "OK", ` test.file`.strip
|
||||
|
||||
# Need complete control over stdin, stdout?
|
||||
require "open3"
|
||||
Open3.popen3("#{bin}/example", "argument") do |stdin, stdout, _|
|
||||
stdin.write("some text")
|
||||
stdin.close
|
||||
assert_equal "result", stdout.read
|
||||
end
|
||||
|
||||
# The test will fail if it returns false, or if an exception is raised.
|
||||
# Failed assertions and failed `system` commands will raise exceptions.
|
||||
end
|
||||
|
||||
|
||||
## Plist handling
|
||||
|
||||
# Does your plist need to be loaded at startup?
|
||||
plist_options :startup => true
|
||||
# Or only when necessary or desired by the user?
|
||||
plist_options :manual => "foo"
|
||||
# Or perhaps you'd like to give the user a choice? Ooh fancy.
|
||||
plist_options :startup => "true", :manual => "foo start"
|
||||
|
||||
# Define this method to provide a plist.
|
||||
# Looking for another example? Check out Apple's handy manpage =>
|
||||
# https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man5/plist.5.html
|
||||
def plist; <<-EOS.undent
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>#{plist_name}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>#{bin}/example</string>
|
||||
<string>--do-this</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/dev/null</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/dev/null</string>
|
||||
</plist>
|
||||
EOS
|
||||
end
|
||||
end
|
||||
|
||||
__END__
|
||||
# Room for a patch after the `__END__`
|
||||
# Read about how to get a patch in here:
|
||||
# https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md
|
||||
# In short, `brew install --interactive --git <formula>` and make your edits.
|
||||
# Then `git diff >> path/to/your/formula.rb`
|
||||
# Note, that HOMEBREW_PREFIX will be replaced in the path before it is
|
||||
# applied. A patch can consit of several hunks.
|
||||
@ -0,0 +1,52 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This file was generated by GoReleaser. DO NOT EDIT.
|
||||
class Syft < Formula
|
||||
desc "A tool that generates a Software Bill Of Materials (SBOM) from container images and filesystems"
|
||||
homepage "https://github.com/anchore/syft"
|
||||
version "1.23.1"
|
||||
license "Apache License 2.0"
|
||||
|
||||
on_macos do
|
||||
if Hardware::CPU.intel?
|
||||
url "https://github.com/anchore/syft/releases/download/v1.23.1/syft_1.23.1_darwin_amd64.tar.gz"
|
||||
sha256 "76fed9a16fec65c2b13f30e2db6128f625aaf54b82302b427a0e2bbb554c6ab7"
|
||||
|
||||
def install
|
||||
bin.install "syft"
|
||||
end
|
||||
end
|
||||
if Hardware::CPU.arm?
|
||||
url "https://github.com/anchore/syft/releases/download/v1.23.1/syft_1.23.1_darwin_arm64.tar.gz"
|
||||
sha256 "099f506860bcb5d85d4a981b4fca7a732978d0eeff79876648cc1a5350974f33"
|
||||
|
||||
def install
|
||||
bin.install "syft"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
on_linux do
|
||||
if Hardware::CPU.intel?
|
||||
if Hardware::CPU.is_64_bit?
|
||||
url "https://github.com/anchore/syft/releases/download/v1.23.1/syft_1.23.1_linux_amd64.tar.gz"
|
||||
sha256 "42f3e01b64f054d0caee42073cb94e3ac3e61be6f0100e7ecda96e6a2abf7e22"
|
||||
|
||||
def install
|
||||
bin.install "syft"
|
||||
end
|
||||
end
|
||||
end
|
||||
if Hardware::CPU.arm?
|
||||
if Hardware::CPU.is_64_bit?
|
||||
url "https://github.com/anchore/syft/releases/download/v1.23.1/syft_1.23.1_linux_arm64.tar.gz"
|
||||
sha256 "6172794c95aebb5c3e84760d6489d1c149762822e254a2e3d413923c1b4263e4"
|
||||
|
||||
def install
|
||||
bin.install "syft"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,3 @@
|
||||
desc "A test Homebrew formula for Foo"
|
||||
homepage "https://example.com/foo"
|
||||
license "Apache 2.0"
|
||||
@ -0,0 +1,6 @@
|
||||
|
||||
desc "A test Homebrew formula for bar"
|
||||
homepage "https://example.com/bar"
|
||||
license "MIT"
|
||||
name "bar"
|
||||
version "4.5.6"
|
||||
7
syft/pkg/homebrew.go
Normal file
7
syft/pkg/homebrew.go
Normal file
@ -0,0 +1,7 @@
|
||||
package pkg
|
||||
|
||||
type HomebrewFormula struct {
|
||||
Tap string `json:"tap,omitempty"`
|
||||
Homepage string `json:"homepage,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
@ -48,6 +48,7 @@ const (
|
||||
SwiplPackPkg Type = "swiplpack"
|
||||
TerraformPkg Type = "terraform"
|
||||
WordpressPluginPkg Type = "wordpress-plugin"
|
||||
HomebrewPkg Type = "homebrew"
|
||||
)
|
||||
|
||||
// AllPkgs represents all supported package types
|
||||
@ -90,6 +91,7 @@ var AllPkgs = []Type{
|
||||
SwiplPackPkg,
|
||||
TerraformPkg,
|
||||
WordpressPluginPkg,
|
||||
HomebrewPkg,
|
||||
}
|
||||
|
||||
// PackageURLType returns the PURL package type for the current package.
|
||||
@ -162,6 +164,8 @@ func (t Type) PackageURLType() string {
|
||||
return "terraform"
|
||||
case WordpressPluginPkg:
|
||||
return "wordpress-plugin"
|
||||
case HomebrewPkg:
|
||||
return "homebrew"
|
||||
default:
|
||||
// TODO: should this be a "generic" purl type instead?
|
||||
return ""
|
||||
@ -246,6 +250,8 @@ func TypeByName(name string) Type {
|
||||
return TerraformPkg
|
||||
case "wordpress-plugin":
|
||||
return WordpressPluginPkg
|
||||
case "homebrew":
|
||||
return HomebrewPkg
|
||||
default:
|
||||
return UnknownPkg
|
||||
}
|
||||
|
||||
@ -147,6 +147,7 @@ func TestTypeFromPURL(t *testing.T) {
|
||||
expectedTypes.Remove(string(LinuxKernelModulePkg))
|
||||
expectedTypes.Remove(string(GithubActionPkg), string(GithubActionWorkflowPkg))
|
||||
expectedTypes.Remove(string(WordpressPluginPkg))
|
||||
expectedTypes.Remove(string(HomebrewPkg))
|
||||
expectedTypes.Remove(string(TerraformPkg))
|
||||
expectedTypes.Remove(string(GraalVMNativeImagePkg))
|
||||
expectedTypes.Remove(string(PhpPeclPkg)) // we should always consider this a pear package
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
const (
|
||||
// this is the number of packages that should be found in the image-pkg-coverage fixture image
|
||||
// when analyzed with the squashed scope.
|
||||
coverageImageSquashedPackageCount = 42
|
||||
coverageImageSquashedPackageCount = 43
|
||||
)
|
||||
|
||||
func TestPackagesCmdFlags(t *testing.T) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user