Skip to content

Commit

Permalink
Add Dotnet support to build-info-go (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
gailazar300 authored Jun 6, 2022
1 parent 46b4aaf commit f16098b
Show file tree
Hide file tree
Showing 45 changed files with 2,466 additions and 11 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,18 @@ Note: checksums calculation is not yet supported for pip projects.
bi pipenv [pipenv command] [command options]
```

#### Dotnet

```shell
bi dotnet [Dotnet command] [command options]
```

#### Nuget

```shell
bi nuget [Nuget command] [command options]
```

Note: checksums calculation is not yet supported for pipenv projects.

#### Conversion to CycloneDX
Expand Down Expand Up @@ -496,6 +508,24 @@ artifact1 := entities.Artifact{Name: "json", Type: "tgz", Checksum: &entities.Ch
err = yarnModule.AddArtifacts(artifact1, artifact2, ...)
```

#### Dotnet

```go
// You can pass an empty string as an argument, if the root of the Dotnet project is the working directory.
dotnetModule, err := bld.AddDotnetModules(nugetProjectPath)
// Calculate the dependencies used by this module, and store them in the module struct.
err = dotnetModule.CalcDependencies()
```

#### Nuget

```go
// You can pass an empty string as an argument, if the root of the Nuget project is the working directory.
nugetModule, err := bld.AddNugetModules(nugetProjectPath)
// Calculate the dependencies used by this module, and store them in the module struct.
err = nugetModule.CalcDependencies()
```

### Collecting Environment Variables

Using `CollectEnv()` you can collect environment variables and attach them to the build.
Expand Down
10 changes: 10 additions & 0 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ func (b *Build) AddYarnModule(srcPath string) (*YarnModule, error) {
return newYarnModule(srcPath, b)
}

// AddNugetModules adds a Nuget module to this Build. Pass srcPath as an empty string if the root of the Nuget project is the working directory.
func (b *Build) AddNugetModules(srcPath string) (*DotnetModule, error) {
return newDotnetModule(srcPath, b)
}

// AddDotnetModules adds a Dotnet module to this Build. Pass srcPath as an empty string if the root of the Dotnet project is the working directory.
func (b *Build) AddDotnetModules(srcPath string) (*DotnetModule, error) {
return newDotnetModule(srcPath, b)
}

func (b *Build) CollectEnv() error {
if !b.buildNameAndNumberProvided() {
return errors.New("a build name must be provided in order to collect environment variables")
Expand Down
154 changes: 154 additions & 0 deletions build/dotnet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package build

import (
"github.com/jfrog/build-info-go/build/utils/dotnet"
"github.com/jfrog/build-info-go/build/utils/dotnet/solution"
"github.com/jfrog/build-info-go/utils"
"github.com/jfrog/gofrog/io"
"os"
"path/filepath"
"strings"
)

type DotnetModule struct {
containingBuild *Build
name string
toolchainType dotnet.ToolchainType
subCommand string
argAndFlags []string
solutionPath string
}

// Pass an empty string for srcPath to find the solutions/proj files in the working directory.
func newDotnetModule(srcPath string, containingBuild *Build) (module *DotnetModule, err error) {
if srcPath == "" {
srcPath, err = os.Getwd()
if err != nil {
return nil, err
}
}
return &DotnetModule{solutionPath: srcPath, containingBuild: containingBuild, argAndFlags: []string{"restore"}}, nil
}

func (dm *DotnetModule) SetArgAndFlags(argAndFlags []string) {
dm.argAndFlags = argAndFlags
}

func (dm *DotnetModule) SetName(name string) {
dm.name = name
}

func (dm *DotnetModule) SetSubcommand(subCommand string) {
dm.subCommand = subCommand
}

func (dm *DotnetModule) SetSolutionPath(solutionPath string) {
dm.solutionPath = solutionPath
}

func (dm *DotnetModule) SetArgsAndFlags(argAndFlags []string) {
dm.argAndFlags = argAndFlags
}

func (dm *DotnetModule) SetToolchainType(toolchainType dotnet.ToolchainType) {
dm.toolchainType = toolchainType
}

// CalcDependencies exec all type of dotnet commands - install, update, add, restore.
// Collects the dotnet project's dependencies and saves them in the build-info module.
func (dm *DotnetModule) CalcDependencies() error {
err := dm.runCmd()
if err != nil {
return err
}
if !dm.containingBuild.buildNameAndNumberProvided() {
return nil
}
slnFile, err := dm.updateSolutionPathAndGetFileName()
if err != nil {
return err
}
sol, err := solution.Load(dm.solutionPath, slnFile, dm.containingBuild.logger)
if err != nil {
return err
}
buildInfo, err := sol.BuildInfo(dm.name, dm.containingBuild.logger)
if err != nil {
return err
}
return dm.containingBuild.SaveBuildInfo(buildInfo)
}

// Prepares the dotnet/nuget configuration file within the temp directory
// Runs nuget/dotnet itself with the arguments and flags provided.
func (dm *DotnetModule) runCmd() error {
cmd, err := dm.createCmd()
if err != nil {
return err
}
// To prevent NuGet prompting for credentials
err = os.Setenv("NUGET_EXE_NO_PROMPT", "true")
if err != nil {
return err
}

err = io.RunCmd(cmd)
if err != nil {
return err
}

return nil
}

func (dm *DotnetModule) createCmd() (*dotnet.Cmd, error) {
c, err := dotnet.NewToolchainCmd(dm.toolchainType)
if err != nil {
return nil, err
}
if dm.subCommand != "" {
c.Command = append(c.Command, strings.Split(dm.subCommand, " ")...)
}
c.CommandFlags = dm.argAndFlags
return c, nil
}

func (dm *DotnetModule) updateSolutionPathAndGetFileName() (string, error) {
// The path argument wasn't provided, sln file will be searched under working directory.
if len(dm.argAndFlags) == 0 || strings.HasPrefix(dm.argAndFlags[0], "-") {
return "", nil
}
cmdFirstArg := dm.argAndFlags[0]
exist, err := utils.IsDirExists(cmdFirstArg, false)
if err != nil {
return "", err
}
// The path argument is a directory. sln/project file will be searched under this directory.
if exist {
dm.updateSolutionPath(cmdFirstArg)
return "", err
}
exist, err = utils.IsFileExists(cmdFirstArg, false)
if err != nil {
return "", err
}
if exist {
// The path argument is a .sln file.
if strings.HasSuffix(cmdFirstArg, ".sln") {
dm.updateSolutionPath(filepath.Dir(cmdFirstArg))
return filepath.Base(cmdFirstArg), nil
}
// The path argument is a .*proj/packages.config file.
if strings.HasSuffix(filepath.Ext(cmdFirstArg), "proj") || strings.HasSuffix(cmdFirstArg, "packages.config") {
dm.updateSolutionPath(filepath.Dir(cmdFirstArg))
}
}
return "", nil
}

func (dm *DotnetModule) updateSolutionPath(slnRootPath string) {
if filepath.IsAbs(slnRootPath) {
dm.solutionPath = slnRootPath
} else {
dm.solutionPath = filepath.Join(dm.solutionPath, slnRootPath)
}
}
44 changes: 44 additions & 0 deletions build/dotnet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package build

import (
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
)

func TestUpdateSolutionPathAndGetFileName(t *testing.T) {
workingDir, err := os.Getwd()
assert.NoError(t, err)
tests := []struct {
name string
flags []string
solutionPath string
expectedSlnFile string
expectedSolutionPath string
}{
{"emptyFlags", []string{}, workingDir, "", workingDir},
{"justFlags", []string{"-flag1", "value1", "-flag2", "value2"}, workingDir, "", workingDir},
{"relFileArgRelPath1", []string{filepath.Join("testdata", "dotnet", "slnDir", "sol.sln")}, filepath.Join("rel", "path"), "sol.sln", filepath.Join("rel", "path", "testdata", "dotnet", "slnDir")},
{"relDirArgRelPath2", []string{filepath.Join("testdata", "dotnet", "slnDir")}, filepath.Join("rel", "path"), "", filepath.Join("rel", "path", "testdata", "dotnet", "slnDir")},
{"absFileArgRelPath1", []string{filepath.Join(workingDir, "testdata", "dotnet", "slnDir", "sol.sln")}, filepath.Join(".", "rel", "path"), "sol.sln", filepath.Join(workingDir, "testdata", "dotnet", "slnDir")},
{"absDirArgRelPath2", []string{filepath.Join(workingDir, "testdata", "dotnet", "slnDir"), "-flag", "value"}, filepath.Join(".", "rel", "path"), "", filepath.Join(workingDir, "testdata", "dotnet", "slnDir")},
{"nonExistingFile", []string{filepath.Join(".", "dir1", "sol.sln")}, workingDir, "", workingDir},
{"nonExistingPath", []string{filepath.Join(workingDir, "non", "existing", "path")}, workingDir, "", workingDir},
{"relCsprojFile", []string{filepath.Join("testdata", "dotnet", "slnDir", "proj.csproj")}, filepath.Join("rel", "path"), "", filepath.Join("rel", "path", "testdata", "dotnet", "slnDir")},
{"relVbprojFile", []string{filepath.Join("testdata", "dotnet", "slnDir", "projTwo.vbproj")}, filepath.Join("rel", "path"), "", filepath.Join("rel", "path", "testdata", "dotnet", "slnDir")},
{"absCsprojFile", []string{filepath.Join(workingDir, "testdata", "dotnet", "slnDir", "proj.csproj")}, filepath.Join("rel", "path"), "", filepath.Join(workingDir, "testdata", "dotnet", "slnDir")},
{"absVbprojFile", []string{filepath.Join(workingDir, "testdata", "dotnet", "slnDir", "projTwo.vbproj")}, filepath.Join("rel", "path"), "", filepath.Join(workingDir, "testdata", "dotnet", "slnDir")},
{"relPackagesConfigFile", []string{filepath.Join("testdata", "dotnet", "slnDir", "packages.config")}, filepath.Join("rel", "path"), "", filepath.Join("rel", "path", "testdata", "dotnet", "slnDir")},
{"absPackagesConfigFile", []string{filepath.Join(workingDir, "testdata", "dotnet", "slnDir", "packages.config")}, filepath.Join("rel", "path"), "", filepath.Join(workingDir, "testdata", "dotnet", "slnDir")},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
dc := DotnetModule{solutionPath: test.solutionPath, argAndFlags: test.flags}
slnFile, err := dc.updateSolutionPathAndGetFileName()
assert.NoError(t, err)
assert.Equal(t, test.expectedSlnFile, slnFile)
assert.Equal(t, test.expectedSolutionPath, dc.solutionPath)
})
}
}
1 change: 1 addition & 0 deletions build/testdata/dotnet/slnDir/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a packages.config file for .Net tests
1 change: 1 addition & 0 deletions build/testdata/dotnet/slnDir/proj.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a csproj file for .Net tests
1 change: 1 addition & 0 deletions build/testdata/dotnet/slnDir/projTwo.vbproj
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a vbproj file for .Net tests
1 change: 1 addition & 0 deletions build/testdata/dotnet/slnDir/sol.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a solution file for .Net tests
22 changes: 22 additions & 0 deletions build/utils/dotnet/configfiletemplate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dotnet

const ConfigFileTemplate = `<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
</packageSources>
<packageSourceCredentials>
</packageSourceCredentials>
</configuration>`

const ConfigFileFormat = `<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="JFrogCli" value="%s" protocolVersion="%s" />
</packageSources>
<packageSourceCredentials>
<JFrogCli>
<add key="Username" value="%s" />
<add key="ClearTextPassword" value="%s" />
</JFrogCli>
</packageSourceCredentials>
</configuration>`
Loading

0 comments on commit f16098b

Please sign in to comment.