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 cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ words:
- runcontext
- unmarshals
- usgovcloudapi
- jdbc
languageSettings:
- languageId: go
ignoreRegExpList:
Expand Down
5 changes: 4 additions & 1 deletion cli/azd/internal/appdetect/appdetect.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/azure/azure-dev/cli/azd/pkg/tools/maven"
"github.com/bmatcuk/doublestar/v4"
)

Expand Down Expand Up @@ -179,7 +180,9 @@ type projectDetector interface {
var allDetectors = []projectDetector{
// Order here determines precedence when two projects are in the same directory.
// This is unlikely to occur in practice, but reordering could help to break the tie in these cases.
&javaDetector{},
&javaDetector{
mvnCli: maven.NewCli(exec.NewCommandRunner(nil)),
},
&dotNetAppHostDetector{
// TODO(ellismg): Remove ambient authority.
dotnetCli: dotnet.NewCli(exec.NewCommandRunner(nil)),
Expand Down
15 changes: 7 additions & 8 deletions cli/azd/internal/appdetect/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import (
"fmt"
"io/fs"
"maps"
"os"
"path/filepath"
"slices"
"strings"

"github.com/azure/azure-dev/cli/azd/pkg/tools/maven"
)

type javaDetector struct {
mvnCli *maven.Cli
rootProjects []mavenProject
}

Expand All @@ -24,7 +26,7 @@ func (jd *javaDetector) DetectProject(ctx context.Context, path string, entries
for _, entry := range entries {
if strings.ToLower(entry.Name()) == "pom.xml" {
pomFile := filepath.Join(path, entry.Name())
project, err := readMavenProject(pomFile)
project, err := readMavenProject(ctx, jd.mvnCli, pomFile)
if err != nil {
return nil, fmt.Errorf("error reading pom.xml: %w", err)
}
Expand Down Expand Up @@ -104,19 +106,16 @@ type plugin struct {
Version string `xml:"version"`
}

func readMavenProject(filePath string) (*mavenProject, error) {
bytes, err := os.ReadFile(filePath)
func readMavenProject(ctx context.Context, mvnCli *maven.Cli, filePath string) (*mavenProject, error) {
effectivePom, err := mvnCli.EffectivePom(ctx, filePath)
if err != nil {
return nil, err
}

var project mavenProject
if err := xml.Unmarshal(bytes, &project); err != nil {
if err := xml.Unmarshal([]byte(effectivePom), &project); err != nil {
return nil, fmt.Errorf("parsing xml: %w", err)
}

project.path = filepath.Dir(filePath)

return &project, nil
}

Expand Down
307 changes: 307 additions & 0 deletions cli/azd/internal/appdetect/java_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
package appdetect

import (
"context"
"log/slog"
"os"
osexec "os/exec"
"path/filepath"
"testing"

"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/tools/maven"
)

func TestToMavenProject(t *testing.T) {
path, err := osexec.LookPath("java")
if err != nil {
t.Skip("Skip readMavenProject because java command doesn't exist.")
} else {
slog.Info("Java command found.", "path", path)
}
path, err = osexec.LookPath("mvn")
if err != nil {
t.Skip("Skip readMavenProject because mvn command doesn't exist.")
} else {
slog.Info("Java command found.", "path", path)
}
tests := []struct {
name string
testPoms []testPom
expected []dependency
}{
{
name: "Test with two dependencies",
testPoms: []testPom{
{
pomFilePath: "pom.xml",
pomContentString: `
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-project</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
`,
},
},
expected: []dependency{
{
GroupId: "org.springframework",
ArtifactId: "spring-core",
Version: "5.3.8",
Scope: "compile",
},
{
GroupId: "junit",
ArtifactId: "junit",
Version: "4.13.2",
Scope: "test",
},
},
},
{
name: "Test with no dependencies",
testPoms: []testPom{
{
pomFilePath: "pom.xml",
pomContentString: `
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-project</artifactId>
<version>1.0.0</version>
<dependencies>
</dependencies>
</project>
`,
},
},
expected: []dependency{},
},
{
name: "Test with one dependency which version is decided by dependencyManagement",
testPoms: []testPom{
{
pomFilePath: "pom.xml",
pomContentString: `
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-project</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
`,
},
},
expected: []dependency{
{
GroupId: "org.slf4j",
ArtifactId: "slf4j-api",
Version: "2.0.4",
Scope: "compile",
},
},
},
{
name: "Test with one dependency which version is decided by parent",
testPoms: []testPom{
{
pomFilePath: "pom.xml",
pomContentString: `
<project>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-project</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>
`,
},
},
expected: []dependency{
{
GroupId: "org.slf4j",
ArtifactId: "slf4j-api",
Version: "2.0.4",
Scope: "compile",
},
},
},
{
name: "Test pom with multi modules",
testPoms: []testPom{
{
pomFilePath: "pom.xml",
pomContentString: `
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-multi-module</artifactId>
<version>0.1.0</version>
<packaging>pom</packaging>
<modules>
<module>library</module>
<module>application</module>
</modules>
</project>
`,
},
{
pomFilePath: filepath.Join("application", "pom.xml"),
pomContentString: `
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>application</name>
<description>Demo project for Spring Boot</description>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
`,
},
{
pomFilePath: filepath.Join("library", "pom.xml"),
pomContentString: `
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>library</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>library</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
`,
},
},
expected: []dependency{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
workingDir, err := prepareTestPomFiles(tt.testPoms)
if err != nil {
t.Fatalf("%v", err)
}
testPom := tt.testPoms[0]
pomFilePath := filepath.Join(workingDir, testPom.pomFilePath)

mavenProject, err := readMavenProject(context.TODO(), maven.NewCli(exec.NewCommandRunner(nil)),
pomFilePath)
if err != nil {
t.Fatalf("readMavenProject failed: %v", err)
}

if len(mavenProject.Dependencies) != len(tt.expected) {
t.Fatalf("Expected: %d\nActual: %d", len(tt.expected), len(mavenProject.Dependencies))
}

for i, dep := range mavenProject.Dependencies {
if dep != tt.expected[i] {
t.Errorf("\nExpected: %s\nActual: %s", tt.expected[i], dep)
}
}
})
}
}

type testPom struct {
pomFilePath string
pomContentString string
}

func prepareTestPomFiles(testPoms []testPom) (string, error) {
tempDir, err := os.MkdirTemp("", "prepareTestPomFiles")
if err != nil {
return "", err
}
for _, testPom := range testPoms {
pomPath := filepath.Join(tempDir, testPom.pomFilePath)
err := os.MkdirAll(filepath.Dir(pomPath), 0755)
if err != nil {
return "", err
}
err = os.WriteFile(pomPath, []byte(testPom.pomContentString), 0600)
if err != nil {
return "", err
}
}
return tempDir, nil
}
1 change: 0 additions & 1 deletion cli/azd/internal/appdetect/testdata/java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent></parent>
Comment thread
weikanglim marked this conversation as resolved.
<groupId>nothing</groupId>
<artifactId>basic-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
Expand Down
Loading