Skip to content
Draft
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
55 changes: 51 additions & 4 deletions artifactory/commands/nuget/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -125,9 +126,12 @@
c.workingDir = workingDir
}

// .slnx is an SDK-only solution format; nuget.exe has no parser for it.
// .slnx is only understood by nuget.exe >= 7.0.0 (NuGet 7 / .NET SDK 10). Check the nuget.exe
// that will actually run, so newer versions are passed through untouched.
if c.toolchainType == dotnetutils.Nuget && hasSlnxTarget(c.args) {
return fmt.Errorf(".slnx solution files are not supported by nuget.exe; use 'jf dotnet restore' instead")
if err := checkSlnxSupport(nugetExeVersion()); err != nil {
return err
}
}

// Decide up front whether the native client performs the upload itself, so the decision is
Expand Down Expand Up @@ -736,8 +740,6 @@
}
}

// hasSlnxTarget returns true if any positional arg is a .slnx file. Used to detect SDK-only
// solution formats before passing them to nuget.exe, which has no .slnx parser.
// performsRestore reports whether the subcommand downloads packages and therefore needs the
// Artifactory source declared.
//
Expand Down Expand Up @@ -801,6 +803,51 @@
return dirs
}

// minSlnxNugetMajor is the first nuget.exe major version that can restore .slnx solutions.
const minSlnxNugetMajor = 7

var nugetVersionRegexp = regexp.MustCompile(`(?i)NuGet Version:\s*(\d+)\.(\d+)(?:\.(\d+))?`)

// nugetExeVersion returns the version of the nuget.exe found on PATH, or "" if it cannot be
// determined (not installed, unexpected output).
func nugetExeVersion() string {
out, err := exec.Command("nuget", "help").CombinedOutput()
if err != nil {
log.Debug("Could not determine nuget.exe version:", err.Error())
return ""
}
return parseNugetVersion(string(out))
}

// parseNugetVersion extracts "X.Y[.Z]" from the "NuGet Version: X.Y.Z.B" banner printed by
// 'nuget help'. Returns "" if the banner is not found.
func parseNugetVersion(output string) string {
m := nugetVersionRegexp.FindStringSubmatch(output)
if m == nil {
return ""
}
v := m[1] + "." + m[2]
if m[3] != "" {
v += "." + m[3]
}
return v
}

// checkSlnxSupport fails when the nuget.exe version is known and older than 7.0.0. An unknown
// version is not blocked: nuget.exe reports its own error if it cannot handle the file.
func checkSlnxSupport(version string) error {
if version == "" {
return nil
}
major, err := strconv.Atoi(strings.SplitN(version, ".", 2)[0])
if err != nil || major >= minSlnxNugetMajor {
return nil

Check failure on line 844 in artifactory/commands/nuget/command.go

View workflow job for this annotation

GitHub Actions / Static Check ubuntu-latest

error is not nil (line 842) but it returns nil (nilerr)

Check failure on line 844 in artifactory/commands/nuget/command.go

View workflow job for this annotation

GitHub Actions / Static Check ubuntu-latest

error is not nil (line 842) but it returns nil (nilerr)
}
return fmt.Errorf(".slnx requires nuget.exe >= 7.0.0 (and MSBuild >= 17.13); found %s. "+
"Upgrade nuget.exe or use 'jf dotnet restore' for SDK-style-only solutions", version)
}

// hasSlnxTarget returns true if any positional arg is a .slnx file.
func hasSlnxTarget(args []string) bool {
for _, arg := range args {
if strings.EqualFold(filepath.Ext(arg), ".slnx") {
Expand Down
19 changes: 19 additions & 0 deletions artifactory/commands/nuget/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,3 +509,22 @@ func TestInsertBeforeSeparator(t *testing.T) {
"the caller's backing array beyond len must not be overwritten")
})
}

func TestParseNugetVersion(t *testing.T) {
assert.Equal(t, "7.9.0", parseNugetVersion("NuGet Version: 7.9.0.12\nusage: NuGet <command>"))
assert.Equal(t, "6.13.2", parseNugetVersion("NuGet Version: 6.13.2.1\n"))
assert.Equal(t, "7.0", parseNugetVersion("nuget version: 7.0"))
assert.Equal(t, "", parseNugetVersion("something else"))
assert.Equal(t, "", parseNugetVersion(""))
}

func TestCheckSlnxSupport(t *testing.T) {
assert.NoError(t, checkSlnxSupport("7.0.0"))
assert.NoError(t, checkSlnxSupport("7.9.0"))
assert.NoError(t, checkSlnxSupport("10.1.0"))
assert.NoError(t, checkSlnxSupport(""), "unknown version must not block")
err := checkSlnxSupport("6.13.2")
require.Error(t, err)
assert.Contains(t, err.Error(), ">= 7.0.0")
assert.Contains(t, err.Error(), "found 6.13.2")
}
2 changes: 1 addition & 1 deletion artifactory/docs/nuget/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Common patterns:
$ jf rt bp myBuild 1

Gotchas:
- .slnx solution files are not supported by nuget.exe; use 'jf dotnet restore' for .slnx projects.
- .slnx solution files: use 'jf nuget restore' for solutions with non-SDK-style projects (e.g. packages.config, .sfproj); it requires nuget.exe >= 7.0.0 and MSBuild >= 17.13. For SDK-style-only solutions you can use 'jf dotnet restore' instead.
- Without JFROG_RUN_NATIVE=true, 'jf nuget-config' must be run first to configure repositories.

Related: jf dotnet, jf nuget-config, jf rt build-publish`
Expand Down
Loading