Skip to content
32 changes: 28 additions & 4 deletions buildtools/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,30 @@ func extractPnpmOptionsFromArgs(args []string) (serverDetails *coreConfig.Server
return serverDetails, cleanArgs, buildConfig, nil
}

// shouldRunNuGetFlexPack reports whether the FlexPack (native) path should handle a
// 'jf nuget' / 'jf dotnet' invocation.
//
// JFROG_RUN_NATIVE=true takes precedence over a per-project configuration file. Previously the
// gate was `ShouldRunNative(configFilePath) && !configExists`, so any leftover
// .jfrog/projects/{nuget,dotnet}.yaml silently forced the legacy path even with the
// environment variable set. That was invisible to the user, and because the legacy path does
// not recognise the native-only flags it forwarded them to MSBuild, surfacing as an opaque
// "MSBUILD : error MSB1001: Unknown switch --repo-resolve". The config file is now reported
// and ignored instead.
//
// configFilePath is only used for the warning message; pass configExists to say whether one
// was found. pmName names the package manager for the 'jf <pm>-config' hint.
func shouldRunNuGetFlexPack(configFilePath string, configExists bool, pmName string) bool {
// ShouldRunNative("") is IsFlexPackEnabled() with no config-path condition attached.
if !artutils.ShouldRunNative("") {
return false
}
if configExists {
log.Warn(fmt.Sprintf("JFROG_RUN_NATIVE=true, so the %s configuration at %q is being ignored and the command runs in native (FlexPack) mode. Unset JFROG_RUN_NATIVE to use the legacy 'jf %s-config' path.", pmName, configFilePath, pmName))
}
return true
}

func NugetCmd(c *cli.Context) error {
if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil {
return err
Expand All @@ -1053,8 +1077,8 @@ func NugetCmd(c *cli.Context) error {
return err
}

// FlexPack bypasses all config file requirements (only when no config exists)
if artutils.ShouldRunNative(configFilePath) && !configExists {
// FlexPack bypasses all config file requirements. JFROG_RUN_NATIVE wins over a config file.
if shouldRunNuGetFlexPack(configFilePath, configExists, "nuget") {
return runNugetFlexPackCmd(c, dotnetutils.Nuget)
}

Expand Down Expand Up @@ -1108,8 +1132,8 @@ func DotnetCmd(c *cli.Context) error {
return err
}

// FlexPack bypasses all config file requirements (only when no config exists)
if artutils.ShouldRunNative(configFilePath) && !configExists {
// FlexPack bypasses all config file requirements. JFROG_RUN_NATIVE wins over a config file.
if shouldRunNuGetFlexPack(configFilePath, configExists, "dotnet") {
return runNugetFlexPackCmd(c, dotnetutils.DotnetCore)
}

Expand Down
26 changes: 14 additions & 12 deletions docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,24 @@ func initNativeDockerWithArtTest(t *testing.T) func() {

// initDockerBuildTest initializes test environment for docker build tests with JFROG_RUN_NATIVE enabled
func initDockerBuildTest(t *testing.T) func() {
// Set JFROG_RUN_NATIVE=true for docker build tests
clientTestUtils.SetEnvAndAssert(t, "JFROG_RUN_NATIVE", "true")

// Initialize native docker test setup
// Initialize native docker test setup FIRST. It calls t.Skip when '-test.docker=true' is
// absent, and t.Skip runs runtime.Goexit: this function never returns, so the caller's
// 'defer cleanup()' is never registered. Anything set up before this line therefore leaks
// into every subsequent test in the binary - which is exactly what happened when
// JFROG_RUN_NATIVE was set above it, silently forcing later 'jf nuget'/'jf dotnet' tests
// down the FlexPack path.
cleanupNativeDocker := initNativeDockerWithArtTest(t)

// Set JFROG_RUN_NATIVE=true for docker build tests. Restored via t.Cleanup rather than the
// returned closure so it is undone even if a later helper below skips or fails the test.
clientTestUtils.SetEnvAndAssert(t, "JFROG_RUN_NATIVE", "true")
t.Cleanup(func() {
clientTestUtils.UnSetEnvAndAssert(t, "JFROG_RUN_NATIVE")
})

// if this is an external JFrog instance, no need to setup buildx with insecure registry
if strings.HasPrefix(*tests.JfrogUrl, "https://") {
return func() {
// Restore JFROG_RUN_NATIVE
clientTestUtils.UnSetEnvAndAssert(t, "JFROG_RUN_NATIVE")
// Run native docker cleanup
cleanupNativeDocker()
}
return cleanupNativeDocker
}
// Setup buildx builder with insecure registry config for localhost
builderName := "jfrog-test-builder"
Expand All @@ -115,8 +119,6 @@ func initDockerBuildTest(t *testing.T) func() {
return func() {
// Cleanup buildx builder
cleanupBuilder()
// Restore JFROG_RUN_NATIVE
clientTestUtils.UnSetEnvAndAssert(t, "JFROG_RUN_NATIVE")
// Run native docker cleanup
cleanupNativeDocker()
}
Expand Down
20 changes: 15 additions & 5 deletions docs/buildtools/dotnet/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,38 @@ func GetDescription() string {

func GetArguments() string {
return ` dotnet sub-command
Arguments and options for the dotnet command.`
The dotnet sub-command to run, with its arguments and options.
Supported sub-commands: restore, build, publish, pack, add, and
'nuget push' (see 'Common patterns' below).`
}

func GetAIDescription() string {
return `Run a .NET CLI command (restore, build, pack, push) through JFrog: package restoration is routed via Artifactory and optional build-info is collected.
return `Run a .NET CLI command (restore, build, publish, pack, add, nuget push) through JFrog: package restoration is routed via Artifactory and optional build-info is collected.

When to use:
- Building .NET Core/SDK projects that consume NuGet packages from Artifactory.
- Publishing a .nupkg to Artifactory with 'jf dotnet nuget push'.
- Capturing build-info for .NET pipelines.

Prerequisites:
- The .NET SDK installed (dotnet on PATH).
- 'jf dotnet-config' run once in the project directory.
- A configured server.
- Either JFROG_RUN_NATIVE=true (native/FlexPack mode, no per-project config needed), or
'jf dotnet-config' run once in the project directory (legacy mode).

Common patterns:
$ jf dotnet restore MyApp.sln
$ export JFROG_RUN_NATIVE=true
$ jf dotnet restore MyApp.sln --repo-resolve my-nuget-virtual --server-id my-server
$ jf dotnet build --build-name=my-app --build-number=4
$ jf dotnet pack --configuration Release
$ jf dotnet nuget push MyApp.1.0.0.nupkg --repo my-nuget-local --server-id my-server

Gotchas:
- 'jf dotnet-config' must be run first.
- 'jf dotnet-config' is optional when JFROG_RUN_NATIVE=true. In that mode a per-project
.jfrog/projects/dotnet.yaml is ignored (a warning is printed) and the native path is used.
- Without JFROG_RUN_NATIVE=true, 'jf dotnet-config' must be run first, and the native-only
flags --repo-resolve / --server-id are not supported.
- 'jf dotnet nuget push' is a two-token sub-command; plain 'jf dotnet push' is not a command.
- Mixing 'jf nuget' and 'jf dotnet' configs in the same directory can create confused resolution.

Related: jf dotnet-config, jf nuget`
Expand Down
10 changes: 8 additions & 2 deletions docs/buildtools/nuget/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,21 @@ When to use:

Prerequisites:
- A local nuget binary on PATH.
- 'jf nuget-config' run once in the project directory.
- A configured server.
- Either JFROG_RUN_NATIVE=true (native/FlexPack mode, no per-project config needed), or
'jf nuget-config' run once in the project directory (legacy mode).

Common patterns:
$ jf nuget restore MyApp.sln
$ jf nuget restore --build-name=my-app --build-number=2
$ export JFROG_RUN_NATIVE=true
$ jf nuget restore MyApp.sln --repo-resolve my-nuget-virtual --server-id my-server

Gotchas:
- 'jf nuget-config' must be run first.
- 'jf nuget-config' is optional when JFROG_RUN_NATIVE=true. In that mode a per-project
.jfrog/projects/nuget.yaml is ignored (a warning is printed) and the native path is used.
- Without JFROG_RUN_NATIVE=true, 'jf nuget-config' must be run first, and the native-only
flags --repo-resolve / --server-id are not supported.
- For .NET Core/SDK projects, prefer 'jf dotnet' instead.
- The nuget binary on Linux/macOS often comes from Mono and behaves differently than on Windows.

Expand Down
Loading
Loading