Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func main() {
|-----------|-----------|-----------|
| alpine | APKBUILD | |
| arch | PKGBUILD | |
| bazel | MODULE.json | |
| bower | bower.json | |
| brew | Brewfile | Brewfile.lock.json |
| cargo | Cargo.toml | Cargo.lock |
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
)

require (
github.com/bazelbuild/buildtools v0.0.0-20260121081817-bbf01ec6cb49 // indirect
github.com/git-pkgs/vers v0.1.0 // indirect
github.com/package-url/packageurl-go v0.1.3 // indirect
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/bazelbuild/buildtools v0.0.0-20260121081817-bbf01ec6cb49 h1:dW8MipfRf3+GADvSw4YCbamPWXapvI6nR5aXdPqnIvA=
github.com/bazelbuild/buildtools v0.0.0-20260121081817-bbf01ec6cb49/go.mod h1:PLNUetjLa77TCCziPsz0EI8a6CUxgC+1jgmWv0H25tg=
github.com/git-pkgs/packageurl-go v0.0.0-20260115093137-a0c26f7ee19e h1:HP9nixDfQIsqSBVYoGA9+FoimW8e2vSUg4cCqXLbX08=
github.com/git-pkgs/packageurl-go v0.0.0-20260115093137-a0c26f7ee19e/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0=
github.com/git-pkgs/purl v0.1.1 h1:qArU393mHvk/Swf38XTH6j1wMhvga9PDXrVSUNuPUWI=
Expand Down
1 change: 1 addition & 0 deletions imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package manifests
import (
_ "github.com/git-pkgs/manifests/internal/alpine"
_ "github.com/git-pkgs/manifests/internal/arch"
_ "github.com/git-pkgs/manifests/internal/bazel"
_ "github.com/git-pkgs/manifests/internal/brew"
_ "github.com/git-pkgs/manifests/internal/cargo"
_ "github.com/git-pkgs/manifests/internal/carthage"
Expand Down
125 changes: 125 additions & 0 deletions internal/bazel/bazel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package bazel

import (
"fmt"
"regexp"

"github.com/bazelbuild/buildtools/build"
"github.com/git-pkgs/manifests/internal/core"
)

func init() {
// MODULE.bazel - manifest
core.Register("bazel", core.Manifest, &bazelModuleManifestParser{}, core.ExactMatch("MODULE.bazel"))

}

type bazelModuleManifestParser struct{}

func (p *bazelModuleManifestParser) Parse(filename string, content []byte) ([]core.Dependency, error) {
var deps []core.Dependency

moduleManifestTree, err := build.ParseModule(filename, content)
if err != nil {
return nil, &core.ParseError{Filename: filename, Err: err}
}

for _, expression := range moduleManifestTree.Stmt {
// Dependency statements - bazel_dep() - have CallExpr type
callExperssion, ok := expression.(*build.CallExpr)
if !ok {
continue
}

// Check if statement is a dependency declaration
ident, ok := callExperssion.X.(*build.Ident)
if !ok || ident.Name != "bazel_dep" {
continue
}

parsedDep, err := parseBazelDep(*callExperssion)
if err != nil {
return nil, &core.ParseError{Filename: filename, Err: err}
}

scope := core.Build
if parsedDep.DevDependency {
scope = core.Development
}
deps = append(deps, core.Dependency{
Name: parsedDep.Name,
Version: parsedDep.Version,
Scope: scope,
Direct: true,
})

}
return deps, nil
}

type BazelDep struct {
Name string
Version string
DevDependency bool
}

type BazelDepParsingError struct {
Message string
Line int
}

var moduleNameRegex = regexp.MustCompile(`^[a-z]([a-z0-9._-]*[a-z0-9])?$`)

func parseBazelDep(callExperssion build.CallExpr) (*BazelDep, error) {
dep := &BazelDep{
DevDependency: false, // default
}

// Check each argument of a dependency statement
// e.g. bazel_dep(name = "google_benchmark", version = "1.9.4", dev_dependency = True,)
for _, arg := range callExperssion.List {
assign, ok := arg.(*build.AssignExpr)
if !ok || assign == nil {
continue
}

keyIdent, ok := assign.LHS.(*build.Ident)
if !ok {
continue
}
switch keyIdent.Name {
case "name":
nameExpr, ok := assign.RHS.(*build.StringExpr)
if !ok {
return nil, &BazelDepParsingError{
Line: callExperssion.ListStart.Line,
Message: "bazel_dep 'name' attribute is not a string"}
}

if !moduleNameRegex.MatchString(nameExpr.Value) {
return nil, &BazelDepParsingError{
Line: callExperssion.ListStart.Line,
Message: "bazel_dep missing required 'name' attribute"}
}
dep.Name = nameExpr.Value

case "version":
if versionExpr, ok := assign.RHS.(*build.StringExpr); ok {
dep.Version = versionExpr.Value
}
case "dev_dependency":
if devDependencyExpr, ok := assign.RHS.(*build.Ident); ok {
dep.DevDependency = devDependencyExpr.Name == "True"
}
}
}
return dep, nil
}

func (e *BazelDepParsingError) Error() string {
return fmt.Sprintf(
"%d:%s",
e.Line,
e.Message,
)
}
79 changes: 79 additions & 0 deletions internal/bazel/bazel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package bazel

import (
"os"
"testing"

"github.com/git-pkgs/manifests/internal/core"
)

func TestBazelModuleManifest(t *testing.T) {
content, err := os.ReadFile("../../testdata/bazel/MODULE.bazel")
if err != nil {
t.Fatalf("failed to read fixture: %v", err)
}

parser := &bazelModuleManifestParser{}
deps, err := parser.Parse("MODULE.bazel", content)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}

if len(deps) != 9 {
t.Fatalf("expected 9 dependencies, got %d", len(deps))
}

depMap := make(map[string]core.Dependency)
for _, d := range deps {
depMap[d.Name] = d
}

expected := map[string]struct {
version string
scope core.Scope
}{
"google_benchmark": {"1.9.4", core.Development},
"j2cl": {"20250630", core.Build},
"jsinterop_generator": {"20250812", core.Build},
"jsinterop_base": {"1.1.0", core.Build},
"bazel_skylib": {"1.7.1", core.Build},
"google_bazel_common": {"0.0.1", core.Build},
"rules_java": {"8.13.0", core.Build},
"rules_license": {"1.0.0", core.Build},
"rules_jvm_external": {"6.6", core.Build},
}

for name, exp := range expected {
dep, ok := depMap[name]
if !ok {
t.Errorf("expected %s dependency", name)
continue
}

if dep.Version != exp.version {
t.Errorf("%s version = %q, want %q", name, dep.Version, exp.version)
}

if dep.Scope != exp.scope {
t.Errorf("%s scope = %v, want %v", name, dep.Scope, exp.scope)
}
}
}

func TestBazelModuleManifest_Parse_Error(t *testing.T) {
content, err := os.ReadFile("../../testdata/bazel/MODULE2.bazel")
if err != nil {
t.Fatalf("failed to read fixture: %v", err)
}

parser := &bazelModuleManifestParser{}
result, err := parser.Parse("MODULE.bazel", content)

if err == nil || result != nil {
t.Fatalf("expected error, got nil.")
}

if err.Error() != "failed to parse MODULE.bazel: 5:bazel_dep missing required 'name' attribute" {
t.Fatalf("unexpected error: %v", err)
}
}
63 changes: 63 additions & 0 deletions testdata/bazel/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module(name = "elemental2",
version = "1.3.0",
)

bazel_dep(
name = "google_benchmark",
version = "1.9.4",
dev_dependency = True,
)

bazel_dep(name = "j2cl", version = "20250630")

# Use head j2cl for testing purposes.
archive_override(
module_name = "j2cl",
strip_prefix = "j2cl-master",
urls = ["https://github.com/google/j2cl/archive/master.zip"],
)

bazel_dep(name = "jsinterop_generator", version = "20250812")

# Use head jsinterop-generator for testing purposes.
archive_override(
module_name = "jsinterop_generator",
strip_prefix = "jsinterop-generator-master",
urls = ["https://github.com/google/jsinterop-generator/archive/master.zip"],
)

bazel_dep(name = "jsinterop_base", version = "1.1.0")

# Use head jsinterop-base for testing purposes.
archive_override(
module_name = "jsinterop_base",
strip_prefix = "jsinterop-base-master",
urls = ["https://github.com/google/jsinterop-base/archive/master.zip"],
)

bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "google_bazel_common", version = "0.0.1")
bazel_dep(name = "rules_java", version = "8.13.0")
bazel_dep(name = "rules_license", version = "1.0.0")

# Maven dependencies.

bazel_dep(name = "rules_jvm_external", version = "6.6")


maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
maven.artifact(
artifact = "closure-compiler",
group = "com.google.javascript",
version = "v20240317",
)
use_repo(maven, "maven")

http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
name = "org_gwtproject_gwt",
sha256 = "731879b8e56024a34f36b83655975a474e1ac1dffdfe72724e337976ac0e1749",
strip_prefix = "gwt-073679594c6ead7abe501009f8ba31eb390047fc",
url = "https://github.com/gwtproject/gwt/archive/073679594c6ead7abe501009f8ba31eb390047fc.zip",
)
9 changes: 9 additions & 0 deletions testdata/bazel/MODULE2.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module(name = "elemental2",
version = "1.3.0",
)
# Broken dependency with missing name
bazel_dep(
name = "",
version = "1.9.4",
dev_dependency = True,
)