Skip to content

Commit a410267

Browse files
authored
Merge branch 'main' into RTECO-1648-apm-support-implementation
2 parents 192cf73 + 2227ac7 commit a410267

3 files changed

Lines changed: 167 additions & 4 deletions

File tree

artifactory/commands/mvn/mvn.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type MvnCommand struct {
3535
deploymentDisabled bool
3636
// File path for Maven extractor in which all build's artifacts details will be listed at the end of the build.
3737
buildArtifactsDetailsFile string
38+
// Only consulted in native (FlexPack) mode; set by jf mvnw to require a Maven Wrapper.
39+
preferWrapper bool
3840
}
3941

4042
func NewMvnCommand() *MvnCommand {
@@ -71,6 +73,13 @@ func (mc *MvnCommand) SetInsecureTls(insecureTls bool) *MvnCommand {
7173
return mc
7274
}
7375

76+
// SetPreferWrapper is only consulted in native (FlexPack) mode. jf mvnw sets this to true,
77+
// requiring a Maven Wrapper (mvnw/mvnw.cmd) to be present; legacy (config-file) mode ignores it.
78+
func (mc *MvnCommand) SetPreferWrapper(preferWrapper bool) *MvnCommand {
79+
mc.preferWrapper = preferWrapper
80+
return mc
81+
}
82+
7483
func (mc *MvnCommand) SetDetailedSummary(detailedSummary bool) *MvnCommand {
7584
mc.detailedSummary = detailedSummary
7685
return mc
@@ -182,7 +191,8 @@ func (mc *MvnCommand) Run() error {
182191
mvnParams := NewMvnUtils().
183192
SetConfigPath(mc.configPath).
184193
SetGoals(mc.goals).
185-
SetBuildConf(mc.configuration)
194+
SetBuildConf(mc.configuration).
195+
SetPreferWrapper(mc.preferWrapper)
186196
return RunMvn(mvnParams)
187197
}
188198

artifactory/commands/mvn/utils.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type MvnUtils struct {
3434
insecureTls bool
3535
disableDeploy bool
3636
outputWriter io.Writer
37+
preferWrapper bool
3738
}
3839

3940
func NewMvnUtils() *MvnUtils {
@@ -85,16 +86,54 @@ func (mu *MvnUtils) SetOutputWriter(writer io.Writer) *MvnUtils {
8586
return mu
8687
}
8788

89+
// SetPreferWrapper controls Maven executable resolution in native (FlexPack) mode.
90+
// When true (jf mvnw), a Maven Wrapper (mvnw/mvnw.cmd) must be found upward from the
91+
// working directory, or the command fails. When false (jf mvn), a wrapper is never
92+
// used; native mode always runs "mvn" from PATH.
93+
func (mu *MvnUtils) SetPreferWrapper(preferWrapper bool) *MvnUtils {
94+
mu.preferWrapper = preferWrapper
95+
return mu
96+
}
97+
98+
// resolveMavenExecutable determines which Maven executable native (FlexPack) mode should run.
99+
// jf mvn (preferWrapper=false) always uses "mvn" from PATH, unchanged from prior behavior.
100+
// jf mvnw (preferWrapper=true) searches upward from the working directory for a project root
101+
// containing ".mvn" (the Maven Wrapper marker directory) and requires mvnw/mvnw.cmd there;
102+
// it fails rather than silently falling back to PATH "mvn".
103+
func resolveMavenExecutable(preferWrapper bool) (string, error) {
104+
if !preferWrapper {
105+
return "mvn", nil
106+
}
107+
projectRoot, exists, err := fileutils.FindUpstream(".mvn", fileutils.Dir)
108+
if err != nil {
109+
return "", errorutils.CheckError(err)
110+
}
111+
if exists {
112+
wrapperName := "mvnw"
113+
if coreutils.IsWindows() {
114+
wrapperName = "mvnw.cmd"
115+
}
116+
wrapperPath := filepath.Join(projectRoot, wrapperName)
117+
if _, statErr := os.Stat(wrapperPath); statErr == nil {
118+
return wrapperPath, nil
119+
}
120+
}
121+
return "", errorutils.CheckErrorf("mvnw invoked but no Maven Wrapper (mvnw/mvnw.cmd) was found in the current directory or any parent directory")
122+
}
123+
88124
func RunMvn(mu *MvnUtils) error {
89125
// FlexPack completely bypasses traditional Maven Build Info Extractor
90126
if utils.ShouldRunNative(mu.configPath) {
91127
log.Debug("Maven native implementation activated")
128+
mavenExecutable, err := resolveMavenExecutable(mu.preferWrapper)
129+
if err != nil {
130+
return err
131+
}
92132
// Execute native Maven command directly (no JFrog Maven plugin)
93-
cmd := exec.Command("mvn", mu.goals...)
133+
cmd := exec.Command(mavenExecutable, mu.goals...)
94134
cmd.Stdout = os.Stdout
95135
cmd.Stderr = os.Stderr
96-
err := cmd.Run()
97-
if err != nil {
136+
if err = cmd.Run(); err != nil {
98137
log.Error("Failed to execute package manager command: " + err.Error())
99138
return errorutils.CheckError(err)
100139
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package mvn
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestResolveMavenExecutable(t *testing.T) {
14+
wrapperName := "mvnw"
15+
if coreutils.IsWindows() {
16+
wrapperName = "mvnw.cmd"
17+
}
18+
19+
tests := []struct {
20+
name string
21+
preferWrapper bool
22+
// setupCwd creates the fixture and returns the directory to chdir into, plus the
23+
// directory the wrapper was actually placed in ("" if no wrapper was created).
24+
setupCwd func(t *testing.T) (cwd string, wrapperRoot string)
25+
expectedExe string
26+
expectErr bool
27+
}{
28+
{
29+
name: "jf mvn without wrapper present - falls back to PATH mvn",
30+
preferWrapper: false,
31+
setupCwd: func(t *testing.T) (string, string) {
32+
return t.TempDir(), ""
33+
},
34+
expectedExe: "mvn",
35+
},
36+
{
37+
name: "jf mvn with wrapper present - still uses PATH mvn (opt-in only via mvnw)",
38+
preferWrapper: false,
39+
setupCwd: func(t *testing.T) (string, string) {
40+
root := t.TempDir()
41+
createWrapperFixture(t, root, wrapperName)
42+
return root, root
43+
},
44+
expectedExe: "mvn",
45+
},
46+
{
47+
name: "jf mvnw with wrapper in cwd - uses wrapper",
48+
preferWrapper: true,
49+
setupCwd: func(t *testing.T) (string, string) {
50+
root := t.TempDir()
51+
createWrapperFixture(t, root, wrapperName)
52+
return root, root
53+
},
54+
},
55+
{
56+
name: "jf mvnw with wrapper only in parent dir - upward search finds it",
57+
preferWrapper: true,
58+
setupCwd: func(t *testing.T) (string, string) {
59+
root := t.TempDir()
60+
createWrapperFixture(t, root, wrapperName)
61+
subDir := filepath.Join(root, "module-a")
62+
require.NoError(t, os.MkdirAll(subDir, 0755))
63+
return subDir, root
64+
},
65+
},
66+
{
67+
name: "jf mvnw without wrapper anywhere - errors, no fallback",
68+
preferWrapper: true,
69+
setupCwd: func(t *testing.T) (string, string) {
70+
return t.TempDir(), ""
71+
},
72+
expectErr: true,
73+
},
74+
}
75+
76+
for _, tt := range tests {
77+
t.Run(tt.name, func(t *testing.T) {
78+
origWd, err := os.Getwd()
79+
require.NoError(t, err)
80+
defer func() {
81+
require.NoError(t, os.Chdir(origWd))
82+
}()
83+
84+
cwd, wrapperRoot := tt.setupCwd(t)
85+
require.NoError(t, os.Chdir(cwd))
86+
87+
exe, err := resolveMavenExecutable(tt.preferWrapper)
88+
if tt.expectErr {
89+
assert.Error(t, err)
90+
return
91+
}
92+
require.NoError(t, err)
93+
if tt.expectedExe != "" {
94+
assert.Equal(t, tt.expectedExe, exe)
95+
return
96+
}
97+
// Compare file identity rather than the path string: Windows CI runners can report
98+
// the same directory via a short (8.3) alias (e.g. "RUNNER~1" vs "runneradmin"),
99+
// which would make a plain string comparison fail even though exe correctly points
100+
// at the wrapper script.
101+
expectedInfo, err := os.Stat(filepath.Join(wrapperRoot, wrapperName))
102+
require.NoError(t, err)
103+
actualInfo, err := os.Stat(exe)
104+
require.NoError(t, err)
105+
assert.True(t, os.SameFile(expectedInfo, actualInfo), "resolved executable %q does not refer to the expected wrapper in %q", exe, wrapperRoot)
106+
})
107+
}
108+
}
109+
110+
// createWrapperFixture creates a minimal Maven Wrapper marker (.mvn dir + wrapper script) at root.
111+
func createWrapperFixture(t *testing.T, root, wrapperName string) {
112+
require.NoError(t, os.MkdirAll(filepath.Join(root, ".mvn", "wrapper"), 0755))
113+
require.NoError(t, os.WriteFile(filepath.Join(root, wrapperName), []byte("#!/bin/sh\n"), 0755))
114+
}

0 commit comments

Comments
 (0)