Skip to content
Open
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
42 changes: 33 additions & 9 deletions cli/internal/utils/project/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,32 @@ type Config struct {
}

type JavaProject struct {
SourceRoot string `yaml:"sourceRoot"`
JavaToolchain string `yaml:"javaToolchain,omitempty"`
Modules []Module `yaml:"modules"`
Dependencies []string `yaml:"dependencies,omitempty"`
SourceRoot string `yaml:"sourceRoot"`
JavaToolchain string `yaml:"javaToolchain,omitempty"`
Modules []Module `yaml:"modules"`
Dependencies []ResolvedDependency `yaml:"dependencies,omitempty"`
}

type ResolvedDependency struct {
Path string `yaml:"path"`
Purl string `yaml:"purl,omitempty"`
}

// UnmarshalYAML accepts a legacy bare path string (as a path-only dependency) or the tagged mapping.
func (d *ResolvedDependency) UnmarshalYAML(unmarshal func(any) error) error {
var bareString string
if err := unmarshal(&bareString); err == nil {
d.Path = bareString
return nil
}

type plain ResolvedDependency
var p plain
if err := unmarshal(&p); err != nil {
return err
}
*d = ResolvedDependency(p)
return nil
}

type GoProject struct {
Expand All @@ -32,10 +54,10 @@ type Module struct {
}

type legacyConfig struct {
SourceRoot string `yaml:"sourceRoot"`
JavaToolchain string `yaml:"javaToolchain,omitempty"`
Modules []Module `yaml:"modules"`
Dependencies []string `yaml:"dependencies,omitempty"`
SourceRoot string `yaml:"sourceRoot"`
JavaToolchain string `yaml:"javaToolchain,omitempty"`
Modules []Module `yaml:"modules"`
Dependencies []ResolvedDependency `yaml:"dependencies,omitempty"`
}

func LoadConfig(projectModelPath string) (*Config, error) {
Expand Down Expand Up @@ -79,7 +101,9 @@ func (c *Config) AllModules() []Module {
func (c *Config) AllDependencies() []string {
var deps []string
for _, jp := range c.JavaProjects {
deps = append(deps, jp.Dependencies...)
for _, d := range jp.Dependencies {
deps = append(deps, d.Path)
}
}
return deps
}
Expand Down
70 changes: 68 additions & 2 deletions cli/internal/utils/project/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"os"
"path/filepath"
"testing"

"gopkg.in/yaml.v2"
)

func writeProjectYaml(t *testing.T, content string) string {
Expand All @@ -23,7 +25,9 @@ javaProjects:
- moduleSourceRoot: java_0/sources
packages: [com.example]
moduleClasses: [java_0/classes/main]
dependencies: [libs/a.jar]
dependencies:
- path: libs/a.jar
purl: pkg:maven/org.opensearch.client/opensearch-rest-client@2.18.0
goProjects:
- projectDir: go_0
`)
Expand All @@ -44,19 +48,45 @@ goProjects:
if got := config.AllDependencies(); len(got) != 1 || got[0] != "libs/a.jar" {
t.Errorf("AllDependencies = %+v", got)
}
dep := config.JavaProjects[0].Dependencies[0]
if dep.Purl != "pkg:maven/org.opensearch.client/opensearch-rest-client@2.18.0" {
t.Fatalf("expected purl-tagged dependency, got %+v", dep)
}
if config.GoProjects[0].ProjectDir != "go_0" {
t.Errorf("go projectDir = %q", config.GoProjects[0].ProjectDir)
}
}

func TestUnmarshalDependencyMappingWithoutPurl(t *testing.T) {
yamlData := []byte(`javaProjects:
- dependencies:
- path: /d/lib.jar
`)

var config Config
if err := yaml.Unmarshal(yamlData, &config); err != nil {
t.Fatalf("yaml.Unmarshal: %v", err)
}

if got := config.AllDependencies(); len(got) != 1 || got[0] != "/d/lib.jar" {
t.Fatalf("AllDependencies = %+v", got)
}
dep := config.JavaProjects[0].Dependencies[0]
if dep.Purl != "" {
t.Errorf("Purl = %q, want empty", dep.Purl)
}
}

func TestLoadConfigLegacyFallback(t *testing.T) {
dir := writeProjectYaml(t, `sourceRoot: src
javaToolchain: /jdk
modules:
- moduleSourceRoot: src
packages: [com.legacy]
moduleClasses: [dist/app.jar]
dependencies: [lib/commons-io.jar]
dependencies:
- path: lib/commons-io.jar
purl: pkg:maven/commons-io/commons-io@2.16.1
`)

config, err := LoadConfig(dir)
Expand All @@ -81,6 +111,42 @@ dependencies: [lib/commons-io.jar]
}
}

func TestLoadConfigLegacyBareStringDependencyLoadsAsPathOnly(t *testing.T) {
dir := writeProjectYaml(t, `javaProjects:
- sourceRoot: src
dependencies:
- /path/to/a.jar
- /path/to/b.jar
`)

config, err := LoadConfig(dir)
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
got := config.AllDependencies()
if len(got) != 2 || got[0] != "/path/to/a.jar" || got[1] != "/path/to/b.jar" {
t.Fatalf("AllDependencies = %+v", got)
}
for _, dep := range config.JavaProjects[0].Dependencies {
if dep.Purl != "" {
t.Errorf("Purl = %q, want empty", dep.Purl)
}
}
}

func TestAllDependenciesReturnsPaths(t *testing.T) {
c := &Config{JavaProjects: []JavaProject{{
Dependencies: []ResolvedDependency{
{Path: "/d/os-2.18.0.jar", Purl: "pkg:maven/org.opensearch.client/opensearch-rest-client@2.18.0"},
{Path: "/d/os-3.5.0.jar", Purl: "pkg:maven/org.opensearch.client/opensearch-rest-client@3.5.0"},
},
}}}
got := c.AllDependencies()
if len(got) != 2 || got[0] != "/d/os-2.18.0.jar" || got[1] != "/d/os-3.5.0.jar" {
t.Fatalf("unexpected: %v", got)
}
}

func TestGetSourceRootRelativeResolved(t *testing.T) {
dir := writeProjectYaml(t, `javaProjects:
- sourceRoot: java_0/sources
Expand Down
12 changes: 9 additions & 3 deletions docs/classes-and-jars-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ javaProjects:
moduleClasses:
- /path/to/module2.jar
dependencies: # optional
- /path/to/dependency1.jar
- /path/to/dependency2.jar
- path: /path/to/opensearch-rest-client-2.18.0.jar
purl: pkg:maven/org.opensearch.client/opensearch-rest-client@2.18.0
- path: /path/to/dependency-without-coordinates.jar # no purl
- /path/to/legacy-bare-string.jar # legacy: loaded path-only
goProjects: # optional
- projectDir: /path/to/go/module
```
Expand All @@ -50,7 +52,11 @@ List of Java projects. Each entry describes one Java project (a multi-module bui
- **moduleSourceRoot** (required): Path to the module's source code directory
- **packages** (required): List of Java packages contained in this module
- **moduleClasses** (required): List of paths to compiled classes or JAR files for this module
- **dependencies** (optional): Array of paths to JAR files the project depends on (typically third-party libraries).
- **dependencies** (optional): The project's dependency JARs (typically third-party libraries). Each entry is an object:
- **path** (required): Path to the JAR file.
- **purl** (optional): The dependency's package-URL (`pkg:<type>/<namespace>/<name>@<version>`, e.g. `pkg:maven/org.opensearch.client/opensearch-rest-client@2.18.0` for JVM, `pkg:golang/...` for Go). Omitted for coordinate-less dependencies (e.g. a JAR supplied via `--dependency`).

For backward compatibility a dependency may also be written as a bare path string (`- /path/to/lib.jar`); it is loaded as a path-only dependency (no purl). New models are generated in the object form above.

### goProjects (optional)
List of Go projects. Each entry has a single field, **projectDir**, pointing at the Go module directory.
Expand Down
Loading