Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

packages.config resolver #114

Merged
merged 8 commits into from
Sep 28, 2023
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
161 changes: 160 additions & 1 deletion internal/resolution/pm/nuget/cmd_factory.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package nuget

import (
"bytes"
"encoding/xml"
"html/template"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
)

type ICmdFactory interface {
Expand All @@ -20,13 +28,55 @@ func (ExecPath) LookPath(file string) (string, error) {
return exec.LookPath(file)
}

var packagesConfigTemplate = `
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>{{.TargetFrameworks}}</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
{{- range .Packages}}
<PackageReference Include="{{.ID}}" Version="{{.Version}}" />
{{- end}}
</ItemGroup>
</Project>
`

type CmdFactory struct {
execPath IExecPath
execPath IExecPath
packageConfgRegex string
packagesConfigTemplate string
}

func NewCmdFactory(execPath IExecPath) CmdFactory {
return CmdFactory{
execPath: execPath,
packageConfgRegex: PackagesConfigRegex,
packagesConfigTemplate: packagesConfigTemplate,
}
}

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

// If the file is a packages.config file, convert it to a .csproj file
// check regex with PackagesConfigRegex
packageConfig, err := regexp.Compile(cmdf.packageConfgRegex)
if err != nil {
return nil, err
}

if packageConfig.MatchString(file) {
file, err = cmdf.convertPackagesConfigToCsproj(file)
if err != nil {
return nil, err
}
}

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

if err != nil {
return nil, err
}

fileDir := filepath.Dir(file)

return &exec.Cmd{
Expand All @@ -37,3 +87,112 @@ func (cmdf CmdFactory) MakeInstallCmd(command string, file string) (*exec.Cmd, e
Dir: fileDir,
}, err
}

type Packages struct {
Packages []Package `xml:"package"`
}

type Package struct {
ID string `xml:"id,attr"`
Version string `xml:"version,attr"`
TargetFramework string `xml:"targetFramework,attr"`
}

// convertPackagesConfigtoCsproj converts a packages.config file to a .csproj file
// this is to enable the use of the dotnet restore command
// 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) {
packages, err := parsePackagesConfig(filePath)
if err != nil {
return "", err
}

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

newFilename := filePath + ".csproj"
err = writeContentToCsprojFile(newFilename, csprojContent)
if err != nil {
return "", err
}

return newFilename, nil
}

var ioReadAllCsproj = io.ReadAll

func parsePackagesConfig(filePath string) (*Packages, error) {
xmlFile, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer xmlFile.Close()

byteValue, err := ioReadAllCsproj(xmlFile)
if err != nil {
return nil, err
}

var packages Packages
err = xml.Unmarshal(byteValue, &packages)
if err != nil {
return nil, err
}

return &packages, nil
}

func collectUniqueTargetFrameworks(packages []Package) string {
uniqueTargetFrameworks := make(map[string]struct{})
for _, pkg := range packages {
uniqueTargetFrameworks[pkg.TargetFramework] = struct{}{}
}

var targetFrameworks []string
for framework := range uniqueTargetFrameworks {
if framework != "" {
targetFrameworks = append(targetFrameworks, framework)
}
}

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
}

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

return tpl.String(), nil
}

var osCreateCsproj = os.Create

func writeContentToCsprojFile(newFilename string, content string) error {

csprojFile, err := osCreateCsproj(newFilename)
if err != nil {
return err
}
defer csprojFile.Close()

_, err = csprojFile.WriteString(content)

return err
}
Loading