-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add plugin for Swift Package Manager
Signed-off-by: Mattt Zmuda <[email protected]>
- Loading branch information
Showing
17 changed files
with
561 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
* PIP (Python) | ||
* Pipenv (Python) | ||
* Gems (Ruby) | ||
* Swift Package Manager (Swift) | ||
|
||
## Installation | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package swift | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
var errDependenciesNotFound error = errors.New("unable to generate SPDX file, no modules or vendors found. Please install them before running spdx-sbom-generator, e.g.: `swift build`") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package swift | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"os/exec" | ||
"path/filepath" | ||
|
||
"github.com/spdx/spdx-sbom-generator/pkg/helper" | ||
"github.com/spdx/spdx-sbom-generator/pkg/models" | ||
) | ||
|
||
type pkg struct { | ||
metadata models.PluginMetadata | ||
} | ||
|
||
const ( | ||
ManifestFile string = "Package.swift" | ||
BuildDirectory string = ".build" | ||
) | ||
|
||
// New creates a new Swift package instance | ||
func New() *pkg { | ||
return &pkg{ | ||
metadata: models.PluginMetadata{ | ||
Name: "Swift Package Manager", | ||
Slug: "swift", | ||
Manifest: []string{ManifestFile}, | ||
ModulePath: []string{BuildDirectory}, | ||
}, | ||
} | ||
} | ||
|
||
// GetVersion returns Swift language version | ||
func (m *pkg) GetVersion() (string, error) { | ||
cmd := exec.Command("swift", "--version") | ||
output, err := cmd.Output() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
version := string(output) | ||
|
||
return version, nil | ||
} | ||
|
||
// GetMetadata returns root package information base on path given | ||
func (m *pkg) GetMetadata() models.PluginMetadata { | ||
return m.metadata | ||
} | ||
|
||
// SetRootModule sets root package information base on path given | ||
func (m *pkg) SetRootModule(path string) error { | ||
return nil | ||
} | ||
|
||
// GetRootModule returns root package information base on path given | ||
func (m *pkg) GetRootModule(path string) (*models.Module, error) { | ||
cmd := exec.Command("swift", "package", "describe", "--type", "json") | ||
cmd.Dir = path | ||
output, err := cmd.Output() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var description SwiftPackageDescription | ||
if err := json.NewDecoder(bytes.NewReader(output)).Decode(&description); err != nil { | ||
return nil, err | ||
} | ||
|
||
mod := description.Module() | ||
|
||
return mod, nil | ||
} | ||
|
||
// ListUsedModules fetches and lists | ||
// all packages required by the project | ||
// in the given project directory, | ||
// this is a plain list of all used modules | ||
// (no nested or tree view) | ||
func (m *pkg) ListUsedModules(path string) ([]models.Module, error) { | ||
cmd := exec.Command("swift", "package", "show-dependencies", "--disable-automatic-resolution", "--format", "json") | ||
cmd.Dir = path | ||
output, err := cmd.Output() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var root SwiftPackageDependency | ||
if err := json.NewDecoder(bytes.NewReader(output)).Decode(&root); err != nil { | ||
return nil, err | ||
} | ||
|
||
var dependencies []SwiftPackageDependency | ||
|
||
var recurse func(SwiftPackageDependency) | ||
recurse = func(dep SwiftPackageDependency) { | ||
for _, nested := range dep.Dependencies { | ||
dependencies = append(dependencies, nested) | ||
recurse(nested) | ||
} | ||
} | ||
recurse(root) | ||
|
||
var collection []models.Module | ||
for _, dep := range dependencies { | ||
mod := dep.Module() | ||
collection = append(collection, *mod) | ||
} | ||
|
||
return collection, nil | ||
} | ||
|
||
// ListModulesWithDeps fetches and lists all packages | ||
// (root and direct dependencies) | ||
// required by the project in the given project directory (side-by-side), | ||
// this is a one level only list of all used modules, | ||
// and each with its direct dependency only | ||
// (similar output to ListUsedModules but with direct dependency only) | ||
func (m *pkg) ListModulesWithDeps(path string) ([]models.Module, error) { | ||
var collection []models.Module | ||
|
||
mod, err := m.GetRootModule(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
collection = append(collection, *mod) | ||
|
||
cmd := exec.Command("swift", "package", "show-dependencies", "--disable-automatic-resolution", "--format", "json") | ||
cmd.Dir = path | ||
output, err := cmd.Output() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var root SwiftPackageDependency | ||
if err := json.NewDecoder(bytes.NewReader(output)).Decode(&root); err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, dep := range root.Dependencies { | ||
mod := dep.Module() | ||
collection = append(collection, *mod) | ||
} | ||
|
||
return collection, nil | ||
} | ||
|
||
// IsValid checks if the project dependency file provided in the contract exists | ||
func (m *pkg) IsValid(path string) bool { | ||
return helper.Exists(filepath.Join(path, ManifestFile)) | ||
} | ||
|
||
// HasModulesInstalled checks whether | ||
// the current project (based on given path) | ||
// has the dependent packages installed | ||
func (m *pkg) HasModulesInstalled(path string) error { | ||
if helper.Exists(filepath.Join(path, BuildDirectory)) { | ||
return nil | ||
} | ||
|
||
return errDependenciesNotFound | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package swift | ||
|
||
import ( | ||
"fmt" | ||
"os/exec" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestSwift(t *testing.T) { | ||
t.Run("test is valid", TestIsValid) | ||
t.Run("test has modules installed", TestHasModulesInstalled) | ||
t.Run("test get root module", TestGetRootModule) | ||
t.Run("test list used modules", TestListUsedModules) | ||
t.Run("test list modules with dependencies", TestListModulesWithDeps) | ||
} | ||
|
||
func TestIsValid(t *testing.T) { | ||
n := New() | ||
path := fmt.Sprintf("%s/test", getPath()) | ||
|
||
valid := n.IsValid(path) | ||
invalid := n.IsValid(getPath()) | ||
|
||
// Assert | ||
assert.Equal(t, true, valid) | ||
assert.Equal(t, false, invalid) | ||
} | ||
|
||
func TestHasModulesInstalled(t *testing.T) { | ||
n := New() | ||
path := fmt.Sprintf("%s/test", getPath()) | ||
|
||
installed := n.HasModulesInstalled(path) | ||
assert.NoError(t, installed) | ||
uninstalled := n.HasModulesInstalled(getPath()) | ||
assert.Error(t, uninstalled) | ||
} | ||
|
||
func TestGetRootModule(t *testing.T) { | ||
n := New() | ||
path := fmt.Sprintf("%s/test", getPath()) | ||
mod, err := n.GetRootModule(path) | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, "Example", mod.Name) | ||
assert.Equal(t, "MIT", mod.LicenseConcluded) | ||
} | ||
|
||
func TestListUsedModules(t *testing.T) { | ||
n := New() | ||
path := fmt.Sprintf("%s/test", getPath()) | ||
mods, err := n.ListUsedModules(path) | ||
|
||
assert.NoError(t, err) | ||
|
||
count := 0 | ||
for _, mod := range mods { | ||
if mod.Name == "DeckOfPlayingCards" { | ||
assert.Equal(t, "3.0.4", mod.Version) | ||
assert.Equal(t, "https://github.com/apple/example-package-deckofplayingcards", mod.PackageURL) | ||
assert.Equal(t, "git+https://github.com/apple/example-package-deckofplayingcards.git", mod.PackageDownloadLocation) | ||
count++ | ||
continue | ||
} | ||
|
||
if mod.Name == "FisherYates" { | ||
assert.Equal(t, "2.0.6", mod.Version) | ||
assert.Equal(t, "https://github.com/apple/example-package-fisheryates", mod.PackageURL) | ||
assert.Equal(t, "git+https://github.com/apple/example-package-fisheryates.git", mod.PackageDownloadLocation) | ||
count++ | ||
continue | ||
} | ||
|
||
if mod.Name == "PlayingCard" { | ||
assert.Equal(t, "3.0.5", mod.Version) | ||
assert.Equal(t, "https://github.com/apple/example-package-playingcard", mod.PackageURL) | ||
assert.Equal(t, "git+https://github.com/apple/example-package-playingcard.git", mod.PackageDownloadLocation) | ||
count++ | ||
continue | ||
} | ||
} | ||
|
||
assert.Equal(t, 3, count) | ||
} | ||
|
||
func TestListModulesWithDeps(t *testing.T) { | ||
n := New() | ||
path := fmt.Sprintf("%s/test", getPath()) | ||
mods, err := n.ListModulesWithDeps(path) | ||
|
||
assert.NoError(t, err) | ||
|
||
count := 0 | ||
for _, mod := range mods { | ||
if mod.Name == "Example" { | ||
count++ | ||
continue | ||
} | ||
|
||
if mod.Name == "DeckOfPlayingCards" { | ||
assert.Equal(t, "3.0.4", mod.Version) | ||
assert.Equal(t, "https://github.com/apple/example-package-deckofplayingcards", mod.PackageURL) | ||
assert.Equal(t, "git+https://github.com/apple/example-package-deckofplayingcards.git", mod.PackageDownloadLocation) | ||
count++ | ||
continue | ||
} | ||
} | ||
|
||
assert.Equal(t, 2, count) | ||
} | ||
|
||
func getPath() string { | ||
cmd := exec.Command("pwd") | ||
output, err := cmd.Output() | ||
if err != nil { | ||
return "" | ||
} | ||
path := strings.TrimSuffix(string(output), "\n") | ||
|
||
return path | ||
} |
Oops, something went wrong.