Skip to content

Commit

Permalink
Merge pull request #123 from debricked/improve-packages-config-resolver
Browse files Browse the repository at this point in the history
Improve packages.config resolution
  • Loading branch information
sweoggy authored Oct 2, 2023
2 parents 9554a96 + d87b868 commit 39258a1
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 45 deletions.
109 changes: 80 additions & 29 deletions internal/resolution/pm/nuget/cmd_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

type ICmdFactory interface {
MakeInstallCmd(command string, file string) (*exec.Cmd, error)
GetTempoCsproj() string
}

type IExecPath interface {
Expand Down Expand Up @@ -45,17 +46,29 @@ type CmdFactory struct {
execPath IExecPath
packageConfgRegex string
packagesConfigTemplate string
tempoCsproj string
}

func NewCmdFactory(execPath IExecPath) CmdFactory {
return CmdFactory{
func NewCmdFactory(execPath IExecPath) *CmdFactory {
return &CmdFactory{
execPath: execPath,
packageConfgRegex: PackagesConfigRegex,
packagesConfigTemplate: packagesConfigTemplate,
tempoCsproj: "",
}
}

func (cmdf CmdFactory) MakeInstallCmd(command string, file string) (*exec.Cmd, error) {
func (cmdf *CmdFactory) GetTempoCsproj() string {
return cmdf.tempoCsproj
}

func (cmdf *CmdFactory) MakeInstallCmd(command string, file string) (*exec.Cmd, error) {

path, err := cmdf.execPath.LookPath(command)

if err != nil {
return nil, err
}

// If the file is a packages.config file, convert it to a .csproj file
// check regex with PackagesConfigRegex
Expand All @@ -64,25 +77,26 @@ func (cmdf CmdFactory) MakeInstallCmd(command string, file string) (*exec.Cmd, e
return nil, err
}

fileLockName := "packages.lock.json"
if packageConfig.MatchString(file) {
file, err = cmdf.convertPackagesConfigToCsproj(file)
file, err = cmdf.convertPackagesConfigToCsproj(file, command)
cmdf.tempoCsproj = file
if err != nil {
return nil, err
}
}

path, err := cmdf.execPath.LookPath(command)

if err != nil {
return nil, err
fileLockName = ".packages.config.nuget.debricked.lock"
}

fileDir := filepath.Dir(file)
file = filepath.Base(file)

return &exec.Cmd{
Path: path,
Args: []string{command, "restore",
file,
"--use-lock-file",
"--lock-file-path",
fileLockName,
},
Dir: fileDir,
}, err
Expand All @@ -103,19 +117,22 @@ type Package struct {
// that enables debricked to parse out transitive dependencies.
// This may add some additional framework dependencies that will not show up if
// we only scan the packages.config file.
func (cmdf CmdFactory) convertPackagesConfigToCsproj(filePath string) (string, error) {
func (cmdf *CmdFactory) convertPackagesConfigToCsproj(filePath string, command string) (string, error) {
packages, err := parsePackagesConfig(filePath)
if err != nil {
return "", err
}

targetFrameworksStr := collectUniqueTargetFrameworks(packages.Packages)
targetFrameworksStr, err := collectUniqueTargetFrameworks(packages.Packages, command)
if err != nil {
return "", err
}
csprojContent, err := cmdf.createCsprojContentWithTemplate(targetFrameworksStr, packages.Packages)
if err != nil {
return "", err
}

newFilename := filePath + ".csproj"
newFilename := filePath + ".nuget.debricked.csproj.temp"
err = writeContentToCsprojFile(newFilename, csprojContent)
if err != nil {
return "", err
Expand All @@ -124,8 +141,50 @@ func (cmdf CmdFactory) convertPackagesConfigToCsproj(filePath string) (string, e
return newFilename, nil
}

func (cmdf *CmdFactory) createCsprojContentWithTemplate(targetFrameworksStr string, packages []Package) (string, error) {
tmplParsed, err := template.New("csproj").Parse(cmdf.packagesConfigTemplate)
if err != nil {
return "", err
}

var tpl bytes.Buffer
err = tmplParsed.Execute(&tpl, map[string]interface{}{
"TargetFrameworks": targetFrameworksStr,
"Packages": packages,
})
if err != nil {
return tpl.String(), err
}

return tpl.String(), nil
}

var ioReadAllCsproj = io.ReadAll

func getDotnetVersion(command string) (string, error) {
cmd := exec.Command(command, "--version")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}

return strings.TrimSpace(out.String()), nil
}

// getDefaultFrameworkOfDotnetVersion returns the default target framework for a given version of .NET.
func getDefaultFrameworkOfDotnetVersion(dotnetVersion string) string {
switch {
case strings.HasPrefix(dotnetVersion, "7"):
return "net7.0"
case strings.HasPrefix(dotnetVersion, "6"):
return "net6.0"
default:
return "net6.0"
}
}

func parsePackagesConfig(filePath string) (*Packages, error) {
xmlFile, err := os.Open(filePath)
if err != nil {
Expand All @@ -147,7 +206,7 @@ func parsePackagesConfig(filePath string) (*Packages, error) {
return &packages, nil
}

func collectUniqueTargetFrameworks(packages []Package) string {
func collectUniqueTargetFrameworks(packages []Package, command string) (string, error) {
uniqueTargetFrameworks := make(map[string]struct{})
for _, pkg := range packages {
uniqueTargetFrameworks[pkg.TargetFramework] = struct{}{}
Expand All @@ -162,24 +221,16 @@ func collectUniqueTargetFrameworks(packages []Package) string {

sort.Strings(targetFrameworks) // Sort the targetFrameworks slice

return strings.Join(targetFrameworks, ";")
}
func (cmdf CmdFactory) createCsprojContentWithTemplate(targetFrameworksStr string, packages []Package) (string, error) {
tmplParsed, err := template.New("csproj").Parse(cmdf.packagesConfigTemplate)
if err != nil {
return "", err
}
if len(targetFrameworks) == 0 {
dotnetVersion, err := getDotnetVersion(command)
if err != nil {
return "", err
}

var tpl bytes.Buffer
err = tmplParsed.Execute(&tpl, map[string]interface{}{
"TargetFrameworks": targetFrameworksStr,
"Packages": packages,
})
if err != nil {
return "", err
targetFrameworks = append(targetFrameworks, getDefaultFrameworkOfDotnetVersion(dotnetVersion))
}

return tpl.String(), nil
return strings.Join(targetFrameworks, ";"), nil
}

var osCreateCsproj = os.Create
Expand Down
89 changes: 77 additions & 12 deletions internal/resolution/pm/nuget/cmd_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,45 @@ import (
)

func TestMakeInstallCmd(t *testing.T) {
cmd, err := NewCmdFactory(
cmdf := NewCmdFactory(
ExecPath{},
).MakeInstallCmd(nuget, "file")
)
cmd, err := cmdf.MakeInstallCmd(nuget, "file")
assert.NoError(t, err)
assert.NotNil(t, cmd)
args := cmd.Args
assert.Contains(t, args, "dotnet")
assert.Contains(t, args, "restore")
assert.Contains(t, args, "--use-lock-file")
assert.Contains(t, args, "--lock-file-path")
assert.Contains(t, args, "packages.lock.json")

tmp := cmdf.GetTempoCsproj()
assert.Equal(t, "", tmp)
}

func TestMakeInstallCmdPackagsConfig(t *testing.T) {

cmd, err := NewCmdFactory(
cmdf := NewCmdFactory(
ExecPath{},
).MakeInstallCmd(nuget, "testdata/valid/packages.config")
)
cmd, err := cmdf.MakeInstallCmd(nuget, "testdata/valid/packages.config")
assert.NoError(t, err)
assert.NotNil(t, cmd)
args := cmd.Args
assert.Contains(t, args, "dotnet")
assert.Contains(t, args, "restore")
assert.Contains(t, args, "--use-lock-file")
assert.Contains(t, args, "--lock-file-path")
assert.Contains(t, args, ".packages.config.nuget.debricked.lock")

// Cleanup: Remove the created .csproj file
if err := os.Remove("testdata/valid/packages.config.csproj"); err != nil {
if err := os.Remove("testdata/valid/packages.config.nuget.debricked.csproj.temp"); err != nil {
t.Fatalf("Failed to remove test file: %v", err)
}

tmp := cmdf.GetTempoCsproj()
assert.Equal(t, "testdata/valid/packages.config.nuget.debricked.csproj.temp", tmp)
}

func MockReadAll(r io.Reader) ([]byte, error) {
Expand Down Expand Up @@ -140,7 +154,10 @@ func TestCollectUniqueTargetFrameworks(t *testing.T) {
{TargetFramework: "net46"},
{TargetFramework: "net45"},
}
got := collectUniqueTargetFrameworks(packages)
got, err := collectUniqueTargetFrameworks(packages, nuget)
if err != nil {
t.Errorf("collectUniqueTargetFrameworks() error = %v", err)
}
want := "net45;net46"
if got != want {
t.Errorf("collectUniqueTargetFrameworks() = %v, want %v", got, want)
Expand Down Expand Up @@ -299,10 +316,10 @@ func TestCreateCsprojContent(t *testing.T) {

func TestMakeInstallCmdBadPackagesConfigRegex(t *testing.T) {

cmd, err := CmdFactory{
cmd, err := (&CmdFactory{
execPath: ExecPath{},
packageConfgRegex: "[",
}.MakeInstallCmd(nuget, "file")
}).MakeInstallCmd(nuget, "file")

assert.Error(t, err)
assert.Nil(t, cmd)
Expand Down Expand Up @@ -346,10 +363,10 @@ func (ExecPathErr) LookPath(file string) (string, error) {

func TestMakeInstallCmdExecPathError(t *testing.T) {

cmd, err := CmdFactory{
cmd, err := (&CmdFactory{
execPath: ExecPathErr{},
packageConfgRegex: PackagesConfigRegex,
}.MakeInstallCmd(nuget, "file")
}).MakeInstallCmd(nuget, "file")

assert.Error(t, err)
assert.Nil(t, cmd)
Expand All @@ -359,6 +376,9 @@ func TestMakeInstallCmdExecPathError(t *testing.T) {
func mockCreate(name string) (*os.File, error) {
return nil, fmt.Errorf("mock error")
}

var nugetCommand = nuget

func TestConvertPackagesConfigToCsproj(t *testing.T) {
tests := []struct {
name string
Expand All @@ -379,6 +399,14 @@ func TestConvertPackagesConfigToCsproj(t *testing.T) {
osCreateCsproj = os.Create
},
},
{"Command does not exist", "testdata/missing_framework/packages.config", true, packagesConfigTemplate,
func() {
nugetCommand = "non-existent-command"
},
func() {
nugetCommand = nuget
},
},
}

for _, tt := range tests {
Expand All @@ -396,7 +424,7 @@ func TestConvertPackagesConfigToCsproj(t *testing.T) {
packageConfgRegex: PackagesConfigRegex,
packagesConfigTemplate: tt.packagesConfigTemplate,
}
_, err := cmd.convertPackagesConfigToCsproj(tt.filePath)
_, err := cmd.convertPackagesConfigToCsproj(tt.filePath, nugetCommand)
if (err != nil) != tt.wantError {
t.Errorf("convertPackagesConfigToCsproj(%q) = %v, want error: %v", tt.filePath, err, tt.wantError)
}
Expand All @@ -405,7 +433,44 @@ func TestConvertPackagesConfigToCsproj(t *testing.T) {
}

// Cleanup: Remove the created .csproj file
if err := os.Remove("testdata/valid/packages.config.csproj"); err != nil {
if err := os.Remove("testdata/valid/packages.config.nuget.debricked.csproj.temp"); err != nil {
t.Fatalf("Failed to remove test file: %v", err)
}
}

func TestGetDotnetVersion(t *testing.T) {
version, err := getDotnetVersion(nuget)
if err != nil {
t.Errorf("getDotnetVersion returned an error: %v", err)
}
if version == "" {
t.Errorf("getDotnetVersion returned an empty string")
}

// Test with a non-existent command
_, err = getDotnetVersion("non-existent-command")
if err == nil {
t.Errorf("getDotnetVersion did not return an error")
}
}

func TestGetDefaultFrameworkOfDotnetVersion(t *testing.T) {
tests := []struct {
version string
want string
}{
{"7.0.100", "net7.0"},
{"6.0.100", "net6.0"},
{"5.0.100", "net6.0"},
{"0.0.0", "net6.0"},
}

for _, tt := range tests {
t.Run(tt.version, func(t *testing.T) {
got := getDefaultFrameworkOfDotnetVersion(tt.version)
if got != tt.want {
t.Errorf("getDefaultFrameworkOfDotnetVersion(%q) = %q, want %q", tt.version, got, tt.want)
}
})
}
}
Loading

0 comments on commit 39258a1

Please sign in to comment.