From 165d1ea39784e2e8ec8351b6df232e5a05ea49d0 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 23 Jul 2021 05:49:21 -0700 Subject: [PATCH] Add plugin for Swift Package Manager Signed-off-by: Mattt Zmuda --- Makefile | 1 + README.md | 1 + check-headers.sh | 2 +- go.mod | 1 + go.sum | 5 + pkg/modules/modules.go | 3 + pkg/modules/swift/errors.go | 9 + pkg/modules/swift/handler.go | 165 ++++++++++++++++++ pkg/modules/swift/handler_test.go | 126 +++++++++++++ pkg/modules/swift/helpers.go | 111 ++++++++++++ pkg/modules/swift/models.go | 51 ++++++ pkg/modules/swift/test/.gitignore | 1 + pkg/modules/swift/test/LICENSE.txt | 8 + pkg/modules/swift/test/Package.resolved | 34 ++++ pkg/modules/swift/test/Package.swift | 28 +++ .../swift/test/Sources/Example/Example.swift | 5 + .../Tests/ExampleTests/ExampleTests.swift | 11 ++ 17 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 pkg/modules/swift/errors.go create mode 100644 pkg/modules/swift/handler.go create mode 100644 pkg/modules/swift/handler_test.go create mode 100644 pkg/modules/swift/helpers.go create mode 100644 pkg/modules/swift/models.go create mode 100644 pkg/modules/swift/test/.gitignore create mode 100644 pkg/modules/swift/test/LICENSE.txt create mode 100644 pkg/modules/swift/test/Package.resolved create mode 100644 pkg/modules/swift/test/Package.swift create mode 100644 pkg/modules/swift/test/Sources/Example/Example.swift create mode 100644 pkg/modules/swift/test/Tests/ExampleTests/ExampleTests.swift diff --git a/Makefile b/Makefile index 079faa1..20650e9 100644 --- a/Makefile +++ b/Makefile @@ -75,3 +75,4 @@ test: prepare-test prepare-test: @cd pkg/modules/npm/test && npm install @cd pkg/modules/yarn/test && yarn install + @cd pkg/modules/swift/test && swift build diff --git a/README.md b/README.md index 66bf618..cb62ff9 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ * PIP (Python) * Pipenv (Python) * Gems (Ruby) + * Swift Package Manager (Swift) ## Installation diff --git a/check-headers.sh b/check-headers.sh index 0b1936b..a61cd43 100755 --- a/check-headers.sh +++ b/check-headers.sh @@ -7,7 +7,7 @@ # Exits with a 1 if one or more source files are missing a license header # These are the file patterns we should exclude - these are typically transient files not checked into source control -exclude_pattern='LICENSES/|vendor|node_modules|.venv|.pytest_cache|.idea|version.txt' +exclude_pattern='LICENSES/|vendor|node_modules|pkg/modules/swift/test|.venv|.pytest_cache|.idea|version.txt' files=() echo "Scanning source code..." diff --git a/go.mod b/go.mod index 7c68dd8..4897e26 100644 --- a/go.mod +++ b/go.mod @@ -10,4 +10,5 @@ require ( github.com/spf13/cobra v1.1.3 github.com/stretchr/testify v1.6.1 github.com/vifraa/gopom v0.1.0 + golang.org/x/mod v0.4.2 ) diff --git a/go.sum b/go.sum index 9e0a2c8..6479460 100644 --- a/go.sum +++ b/go.sum @@ -253,6 +253,7 @@ golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -278,6 +279,8 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -345,7 +348,9 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.7.0 h1:Hdks0L0hgznZLG9nzXb8vZ0rRvqNvAcgAp84y7Mwkgw= gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM= diff --git a/pkg/modules/modules.go b/pkg/modules/modules.go index b365c0c..cf2fec6 100644 --- a/pkg/modules/modules.go +++ b/pkg/modules/modules.go @@ -4,6 +4,7 @@ package modules import ( "errors" + "github.com/spdx/spdx-sbom-generator/pkg/modules/javagradle" log "github.com/sirupsen/logrus" @@ -17,6 +18,7 @@ import ( "github.com/spdx/spdx-sbom-generator/pkg/modules/npm" "github.com/spdx/spdx-sbom-generator/pkg/modules/nuget" "github.com/spdx/spdx-sbom-generator/pkg/modules/pip" + "github.com/spdx/spdx-sbom-generator/pkg/modules/swift" "github.com/spdx/spdx-sbom-generator/pkg/modules/yarn" ) @@ -40,6 +42,7 @@ func init() { nuget.New(), yarn.New(), pip.New(), + swift.New(), ) } diff --git a/pkg/modules/swift/errors.go b/pkg/modules/swift/errors.go new file mode 100644 index 0000000..70564ce --- /dev/null +++ b/pkg/modules/swift/errors.go @@ -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`") diff --git a/pkg/modules/swift/handler.go b/pkg/modules/swift/handler.go new file mode 100644 index 0000000..fb86bcc --- /dev/null +++ b/pkg/modules/swift/handler.go @@ -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 +} diff --git a/pkg/modules/swift/handler_test.go b/pkg/modules/swift/handler_test.go new file mode 100644 index 0000000..e60ecc8 --- /dev/null +++ b/pkg/modules/swift/handler_test.go @@ -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 +} diff --git a/pkg/modules/swift/helpers.go b/pkg/modules/swift/helpers.go new file mode 100644 index 0000000..fed4d81 --- /dev/null +++ b/pkg/modules/swift/helpers.go @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 + +package swift + +import ( + "bufio" + "fmt" + "os/exec" + "strings" + + "golang.org/x/mod/semver" + + "github.com/spdx/spdx-sbom-generator/pkg/helper" + "github.com/spdx/spdx-sbom-generator/pkg/models" +) + +func (description SwiftPackageDescription) Module() *models.Module { + mod := &models.Module{} + + mod.Name = description.Name + mod.Root = true + mod.LocalPath = description.Path + setLicense(mod, description.Path) + setCheckSum(mod, description.Path) + setVersion(mod, description.Path) + + return mod +} + +func (dep SwiftPackageDependency) Module() *models.Module { + mod := &models.Module{} + mod.Name = dep.Name + mod.PackageURL = strings.TrimSuffix(dep.Url, ".git") + + if strings.HasSuffix(dep.Url, ".git") { + if strings.HasPrefix(dep.Url, "http") || + strings.HasPrefix(dep.Url, "ssh") || + strings.HasPrefix(dep.Url, "git@") { + mod.PackageDownloadLocation = "git+" + dep.Url + } + } + + mod.Version = dep.Version + mod.LocalPath = dep.Path + setLicense(mod, dep.Path) + setCheckSum(mod, dep.Path) + + return mod +} + +func setLicense(mod *models.Module, path string) error { + licensePkg, err := helper.GetLicenses(path) + if err != nil { + return err + } + + mod.LicenseDeclared = helper.BuildLicenseDeclared(licensePkg.ID) + mod.LicenseConcluded = helper.BuildLicenseConcluded(licensePkg.ID) + if !helper.LicenseSPDXExists(licensePkg.ID) { + licensePkg.ID = fmt.Sprintf("LicenseRef-%s", licensePkg.ID) + mod.OtherLicense = append(mod.OtherLicense, licensePkg) + } + mod.Copyright = helper.GetCopyright(licensePkg.ExtractedText) + mod.CommentsLicense = licensePkg.Comments + + return nil +} + +func setVersion(mod *models.Module, path string) error { + cmd := exec.Command("git", "describe", "--tags", "--exact-match") + cmd.Dir = path + output, err := cmd.Output() + if err != nil { + return err + } + + scanner := bufio.NewScanner(strings.NewReader(string(output))) + for scanner.Scan() { + version := scanner.Text() + + // semver requires a "v" prefix + if !strings.HasPrefix(version, "v") { + version = "v" + version + } + + if semver.IsValid(version) { + mod.Version = version[1:] // remove the "v" prefix + break + } + } + + return nil +} + +func setCheckSum(mod *models.Module, path string) error { + cmd := exec.Command("git", "rev-parse", "HEAD") + cmd.Dir = path + output, err := cmd.Output() + if err != nil { + return err + } + + if len(output) > 0 { + mod.CheckSum = &models.CheckSum{ + Algorithm: models.HashAlgoSHA1, // FIXME: derive from git + Value: string(output), + } + } + + return nil +} diff --git a/pkg/modules/swift/models.go b/pkg/modules/swift/models.go new file mode 100644 index 0000000..589d8a1 --- /dev/null +++ b/pkg/modules/swift/models.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 + +package swift + +type SwiftPackageDescription struct { + Name string `json:"name"` + Path string `json:"path"` + + Dependencies []struct { + Url string `json:"url"` + Requirement struct { + Revision []string `json:"revision"` + Range []struct { + LowerBound string `json:"lower_bound"` + UpperBound string `json:"upper_bound"` + } + } `json:"requirement"` + } `json:"dependencies"` + + Platforms []struct { + Name string `json:"name"` + Version string `json:"version"` + } `json:"platforms"` + + Products []struct { + Name string `json:"name"` + Targets []string `json:"targets"` + Type map[string]interface{} `json:"type"` + } `json:"products"` + + Targets []struct { + C99Name string `json:"c99name"` + ModuleType string `json:"module_type"` + Name string `json:"name"` + Path string `json:"path"` + ProductMemberships []string `json:"product_memberships"` + Sources []string `json:"sources"` + TargetDependencies []string `json:"target_dependencies"` + Type string `json:"type"` + } `json:"targets"` + + ToolVersion string `json:"tools_version"` +} + +type SwiftPackageDependency struct { + Name string `json:"name"` + Url string `json:"url"` + Version string `json:"version"` + Path string `json:"path"` + Dependencies []SwiftPackageDependency `json:"dependencies"` +} diff --git a/pkg/modules/swift/test/.gitignore b/pkg/modules/swift/test/.gitignore new file mode 100644 index 0000000..24e5b0a --- /dev/null +++ b/pkg/modules/swift/test/.gitignore @@ -0,0 +1 @@ +.build diff --git a/pkg/modules/swift/test/LICENSE.txt b/pkg/modules/swift/test/LICENSE.txt new file mode 100644 index 0000000..fab2390 --- /dev/null +++ b/pkg/modules/swift/test/LICENSE.txt @@ -0,0 +1,8 @@ +Copyright + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/pkg/modules/swift/test/Package.resolved b/pkg/modules/swift/test/Package.resolved new file mode 100644 index 0000000..4c4bd4a --- /dev/null +++ b/pkg/modules/swift/test/Package.resolved @@ -0,0 +1,34 @@ +{ + "object": { + "pins": [ + { + "package": "example-package-deckofplayingcards", + "repositoryURL": "https://github.com/apple/example-package-deckofplayingcards.git", + "state": { + "branch": null, + "revision": "2c0e5ac3e10216151fc78ac1ec6bd9c2c0111a3a", + "version": "3.0.4" + } + }, + { + "package": "example-package-fisheryates", + "repositoryURL": "https://github.com/apple/example-package-fisheryates.git", + "state": { + "branch": null, + "revision": "e729f197bbc3831b9a3005fa71ad6f38c1e7e17e", + "version": "2.0.6" + } + }, + { + "package": "example-package-playingcard", + "repositoryURL": "https://github.com/apple/example-package-playingcard.git", + "state": { + "branch": null, + "revision": "39ddabb01e8102ab548a8c6bb3eb20b15f3b4fbc", + "version": "3.0.5" + } + } + ] + }, + "version": 1 +} diff --git a/pkg/modules/swift/test/Package.swift b/pkg/modules/swift/test/Package.swift new file mode 100644 index 0000000..23c3c0c --- /dev/null +++ b/pkg/modules/swift/test/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Example", + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "Example", + targets: ["Example"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(name: "DeckOfPlayingCards", url: "https://github.com/apple/example-package-deckofplayingcards.git", from: "3.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "Example", + dependencies: ["DeckOfPlayingCards"]), + .testTarget( + name: "ExampleTests", + dependencies: ["Example"]), + ] +) diff --git a/pkg/modules/swift/test/Sources/Example/Example.swift b/pkg/modules/swift/test/Sources/Example/Example.swift new file mode 100644 index 0000000..1781b7e --- /dev/null +++ b/pkg/modules/swift/test/Sources/Example/Example.swift @@ -0,0 +1,5 @@ +import DeckOfPlayingCards + +struct Example { + var text = "Hello, World!" +} diff --git a/pkg/modules/swift/test/Tests/ExampleTests/ExampleTests.swift b/pkg/modules/swift/test/Tests/ExampleTests/ExampleTests.swift new file mode 100644 index 0000000..7219c54 --- /dev/null +++ b/pkg/modules/swift/test/Tests/ExampleTests/ExampleTests.swift @@ -0,0 +1,11 @@ + import XCTest + @testable import Example + + final class ExampleTests: XCTestCase { + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct + // results. + XCTAssertEqual(Example().text, "Hello, World!") + } + }