Skip to content

Commit

Permalink
Merge pull request #363 from mattt/swift
Browse files Browse the repository at this point in the history
Add support for Swift / Swift Package Manager
  • Loading branch information
jonabc authored May 27, 2021
2 parents 3305e93 + a1e4336 commit c06e0da
Show file tree
Hide file tree
Showing 12 changed files with 285 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,33 @@ jobs:
- name: Run tests
run: script/test pipenv

swift:
runs-on: ubuntu-latest
strategy:
matrix:
swift: [ "5.4", "5.3" ]
steps:
- uses: actions/checkout@v2
- name: Setup Swift
uses: fwal/setup-swift@v1
with:
swift-version: ${{ matrix.swift }}
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6
- run: bundle lock
- uses: actions/cache@v1
with:
path: vendor/gems
key: ${{ runner.os }}-gem-2.6.x-${{ hashFiles('**/Gemfile.lock') }}
- name: Bootstrap
run: script/bootstrap
- name: Set up fixtures
run: script/source-setup/swift
- name: Run tests
run: script/test swift

yarn:
runs-on: ubuntu-latest
strategy:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ Dependencies will be automatically detected for all of the following sources by
1. [NuGet](./docs/sources/nuget.md)
1. [Pip](./docs/sources/pip.md)
1. [Pipenv](./docs/sources/pipenv.md)
1. [Swift](./docs/sources/swift.md)
1. [Yarn](./docs/sources/yarn.md)

You can disable any of them in the configuration file:
Expand Down
4 changes: 4 additions & 0 deletions docs/sources/swift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Swift

The Swift source uses `swift package` subcommands
to enumerate dependencies and properties.
1 change: 1 addition & 0 deletions lib/licensed/sources.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Sources
require "licensed/sources/nuget"
require "licensed/sources/pip"
require "licensed/sources/pipenv"
require "licensed/sources/swift"
require "licensed/sources/gradle"
require "licensed/sources/mix"
require "licensed/sources/yarn"
Expand Down
64 changes: 64 additions & 0 deletions lib/licensed/sources/swift.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true
require "json"
require "pathname"
require "uri"

module Licensed
module Sources
class Swift < Source
def enabled?
return unless Licensed::Shell.tool_available?("swift") && swift_package?
File.exist?(package_resolved_file_path)
end

def enumerate_dependencies
pins.map { |pin|
name = pin["package"]
version = pin.dig("state", "version")
path = nil
errors = []

begin
path = dependency_path_for_url(pin["repositoryURL"])
rescue => e
errors << e
end

Dependency.new(
name: name,
path: path,
version: version,
errors: errors
)
}
end

private

def pins
return @pins if defined?(@pins)

@pins = begin
json = JSON.parse(File.read(package_resolved_file_path))
json.dig("object", "pins")
rescue => e
message = "Licensed was unable to read the Package.resolved file. Error: #{e.message}"
raise Licensed::Sources::Source::Error, message
end
end

def dependency_path_for_url(url)
last_path_component = URI(url).path.split("/").last.sub(/\.git$/, "")
File.join(config.pwd, ".build", "checkouts", last_path_component)
end

def package_resolved_file_path
File.join(config.pwd, "Package.resolved")
end

def swift_package?
Licensed::Shell.success?("swift", "package", "describe")
end
end
end
end
22 changes: 22 additions & 0 deletions script/source-setup/swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
set -e

if [ -z "$(which swift)" ]; then
echo "A local swift installation is required for swift development." >&2
exit 127
fi

swift --version

BASE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd $BASE_PATH/test/fixtures/swift

if [ "$1" == "-f" ]; then
find . -not -regex "\.*" \
-and -not -path "*/Package.swift" \
-and -not -path "*/Sources*" \
-and -not -path "*/Tests*" \
-print0 | xargs -0 rm -rf
fi

swift package resolve
5 changes: 5 additions & 0 deletions test/fixtures/command/swift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
expected_dependency: DeckOfPlayingCards
source_path: test/fixtures/swift
cache_path: test/fixtures/swift/.licenses
sources:
swift: true
34 changes: 34 additions & 0 deletions test/fixtures/swift/Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"object": {
"pins": [
{
"package": "DeckOfPlayingCards",
"repositoryURL": "https://github.com/apple/example-package-deckofplayingcards.git",
"state": {
"branch": null,
"revision": "2c0e5ac3e10216151fc78ac1ec6bd9c2c0111a3a",
"version": "3.0.4"
}
},
{
"package": "FisherYates",
"repositoryURL": "https://github.com/apple/example-package-fisheryates.git",
"state": {
"branch": null,
"revision": "e729f197bbc3831b9a3005fa71ad6f38c1e7e17e",
"version": "2.0.6"
}
},
{
"package": "PlayingCard",
"repositoryURL": "https://github.com/apple/example-package-playingcard.git",
"state": {
"branch": null,
"revision": "39ddabb01e8102ab548a8c6bb3eb20b15f3b4fbc",
"version": "3.0.5"
}
}
]
},
"version": 1
}
31 changes: 31 additions & 0 deletions test/fixtures/swift/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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: "Fixtures",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "Fixtures",
targets: ["Fixtures"]),
],
dependencies: [
.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: "Fixtures",
dependencies: [
.product(name: "DeckOfPlayingCards", package: "DeckOfPlayingCards")
]),
.testTarget(
name: "FixturesTests",
dependencies: ["Fixtures"]),
]
)
3 changes: 3 additions & 0 deletions test/fixtures/swift/Sources/Fixtures/Fixture.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public struct Fixture {
public init() {}
}
8 changes: 8 additions & 0 deletions test/fixtures/swift/Tests/FixturesTests/FixturesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import XCTest
import Fixtures

class FixturesTests: XCTestCase {
func testFixtures() {
XCTAssertNotNil(Fixture())
}
}
85 changes: 85 additions & 0 deletions test/sources/swift_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true
require "test_helper"
require "tmpdir"
require "pp"

if Licensed::Shell.tool_available?("swift")
describe Licensed::Sources::Swift do
let(:fixtures) { File.expand_path("../../fixtures/swift", __FILE__) }
let(:config) { Licensed::AppConfiguration.new({ "source_path" => Dir.pwd }) }
let(:source) { Licensed::Sources::Swift.new(config) }

describe "enabled?" do
it "is true if Swift package exists" do
Dir.chdir(fixtures) do
assert source.enabled?
end
end

it "is false if Swift package doesn't exist" do
Dir.chdir(Dir.tmpdir) do
refute source.enabled?
end
end
end

describe "enumerate_dependencies" do
it "does not include the source project" do
Dir.chdir(fixtures) do
config["name"] = "Fixtures"
refute source.enumerate_dependencies.find { |d| d.name == "Fixtures" }
end
end

it "finds dependencies from path sources" do
Dir.chdir(fixtures) do
dep = source.enumerate_dependencies.find { |d| d.name == "DeckOfPlayingCards" }
assert dep
assert_equal "3.0.4", dep.version

dep = source.enumerate_dependencies.find { |d| d.name == "FisherYates" }
assert dep
assert_equal "2.0.6", dep.version

dep = source.enumerate_dependencies.find { |d| d.name == "PlayingCard" }
assert dep
assert_equal "3.0.5", dep.version

dep = source.enumerate_dependencies.find { |d| d.name == "Invalid" }
refute dep
end
end

it "handles invalid repositoryURL field" do
source.stubs(:pins).returns(
JSON.parse <<-JSON
[{
"package": "Invalid",
"repositoryURL": "Invalid",
"state": {
"version": "1.0.0"
}
}]
JSON
)

dep = source.enumerate_dependencies.find { |d| d.name == "Invalid" }
assert dep
assert dep.errors
end

it "handles invalid Package.resolved file" do
Dir.mktmpdir do |dir|
FileUtils.cp_r(fixtures, dir)
File.write(File.join(dir, "Package.resolved"), %("Invalid"))

Dir.chdir(dir) do
assert_raises ::Licensed::Sources::Source::Error do
source.enumerate_dependencies
end
end
end
end
end
end
end

0 comments on commit c06e0da

Please sign in to comment.