Skip to content
This repository has been archived by the owner on Jan 4, 2020. It is now read-only.

[WIP]: Multi dependency package support & other proposed fixes/improvements #193

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:4.1
// swift-tools-version:4.2

/**
* Marathon
Expand Down
19 changes: 19 additions & 0 deletions Sources/MarathonCore/Dependency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation

public struct Dependency {
public let name: String?
public var url: URL

public init(name: String? = nil, url: URL) {
self.name = name
self.url = url
}
}

extension Dependency: Equatable {
public static func ==(lhs: Dependency, rhs: Dependency) -> Bool {
return
lhs.name == rhs.name
&& lhs.url == rhs.url
}
}
29 changes: 25 additions & 4 deletions Sources/MarathonCore/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,37 @@ import Releases
public struct Package {
public let name: String
public let url: URL
public var majorVersion: Int
public var version: Version
public var majorVersion: Int { return version.major }
}

extension Package: Equatable {
public static func ==(lhs: Package, rhs: Package) -> Bool {
return lhs.url == rhs.url && lhs.majorVersion == rhs.majorVersion
return lhs.url == rhs.url && lhs.version == rhs.version
}
}

extension Package: Hashable {
public var hashValue: Int {
return "\(url.absoluteString);\(version.description)".hashValue
}
}

extension Package: Unboxable {
public init(unboxer: Unboxer) throws {
name = try unboxer.unbox(key: "name")
url = try unboxer.unbox(key: "url")
majorVersion = try unboxer.unbox(key: "majorVersion")

if let versionData: [String: Int] = try? unboxer.unbox(key: "version")
, let major: Int = versionData["major"]
, let minor: Int = versionData["minor"]
, let patch: Int = versionData["patch"] {
version = Version(major: major, minor: minor, patch: patch, prefix: nil, suffix: nil)
} else {
let majorVersion: Int = try unboxer.unbox(key: "majorVersion")
version = Version(major: majorVersion)
}

}
}

Expand All @@ -41,8 +58,12 @@ internal extension Package {
if toolsVersion.major == 3 {
return ".Package(url: \"\(url.absoluteString)\", majorVersion: \(majorVersion))"
}

if toolsVersion >= Version(major: 4, minor: 2) && !url.isForRemoteRepository {
return ".package(path: \"\(url.absoluteString)\")"
}

return ".package(url: \"\(url.absoluteString)\", from: \"\(majorVersion).0.0\")"
return ".package(url: \"\(url.absoluteString)\", from: \"\(version.major).\(version.minor).\(version.patch)\")"
}
}

Expand Down
121 changes: 86 additions & 35 deletions Sources/MarathonCore/PackageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ public final class PackageManager {
}
}

let latestVersion = try latestMajorVersionForPackage(at: url)
let package = Package(name: name, url: absoluteRepositoryURL(from: url), majorVersion: latestVersion)
let latestVersion = try latestVersionForPackage(at: url)
let package = Package(name: name, url: absoluteRepositoryURL(from: url), version: latestVersion)
try save(package: package)

try updatePackages()
Expand All @@ -124,17 +124,21 @@ public final class PackageManager {
return package
}

public func addPackagesIfNeeded(from packageURLs: [URL]) throws {
let existingPackageURLs = Set(makePackageList().map { package in
return package.url.absoluteString.lowercased()
})
public func addPackagesIfNeeded(from dependencies: [Dependency]) throws -> [Dependency] {
let existingPackages = makePackageList()

for url in packageURLs {
guard !existingPackageURLs.contains(url.absoluteString.lowercased()) else {
continue
return try dependencies.map { dependency throws in
let existingPackage = try existingPackages.first { (package) throws -> Bool in
return package.url.absoluteString.lowercased() == dependency.url.absoluteString.lowercased()
}
guard existingPackage == nil else {
return dependency
}

try addPackage(at: url, throwIfAlreadyAdded: false)
let newPackage = try addPackage(at: dependency.url, throwIfAlreadyAdded: false)
var dep = dependency
dep.url = newPackage.url // make sure that any ~ or trailing slashes are identical by setting a new URL on the dependency
return dep
}
}

Expand Down Expand Up @@ -165,20 +169,10 @@ public final class PackageManager {
}

public func makePackageDescription(for script: Script) throws -> String {
guard let masterDescription = try? generatedFolder.file(named: "Package.swift").readAsString() else {
try updatePackages()
return try makePackageDescription(for: script)
}

let toolsVersion = try resolveSwiftToolsVersion()
let expectedHeader = makePackageDescriptionHeader(forSwiftToolsVersion: toolsVersion)

guard masterDescription.hasPrefix(expectedHeader) else {
try generateMasterPackageDescription(forSwiftToolsVersion: toolsVersion)
return try makePackageDescription(for: script)
}

return masterDescription.replacingOccurrences(of: masterPackageName, with: script.name)
return try generatePackageDescription(for: script, toolsVersion: toolsVersion)
}

public func symlinkPackages(to folder: Folder) throws {
Expand Down Expand Up @@ -219,13 +213,13 @@ public final class PackageManager {

public func updateAllPackagesToLatestMajorVersion() throws {
for var package in addedPackages {
let latestMajorVersion = try latestMajorVersionForPackage(at: package.url)
let latestVersion = try latestVersionForPackage(at: package.url)

guard latestMajorVersion > package.majorVersion else {
guard latestVersion > package.version else {
continue
}

package.majorVersion = latestMajorVersion
package.version = latestVersion
try save(package: package)
}

Expand Down Expand Up @@ -257,7 +251,7 @@ public final class PackageManager {

// MARK: - Private

private func latestMajorVersionForPackage(at url: URL) throws -> Int {
private func latestVersionForPackage(at url: URL) throws -> Version {
printer.reportProgress("Resolving latest major version for \(url.absoluteString)...")

let releases = try perform(Releases.versions(for: url).withoutPreReleases(),
Expand All @@ -267,7 +261,7 @@ public final class PackageManager {
throw Error.failedToResolveLatestVersion(url)
}

return latestVersion.major
return latestVersion
}

private func nameOfPackage(at url: URL) throws -> String {
Expand Down Expand Up @@ -315,14 +309,14 @@ public final class PackageManager {
private func updatePackages() throws {
printer.reportProgress("Updating packages...")

do {
let toolsVersion = try resolveSwiftToolsVersion()
try generateMasterPackageDescription(forSwiftToolsVersion: toolsVersion)
try shellOutToSwiftCommand("package update", in: generatedFolder, printer: printer)
try generatedFolder.createSubfolderIfNeeded(withName: "Packages")
} catch {
throw Error.failedToUpdatePackages(folder)
}
// do {
// let toolsVersion = try resolveSwiftToolsVersion()
// try generateMasterPackageDescription(forSwiftToolsVersion: toolsVersion)
// try shellOutToSwiftCommand("package update", in: generatedFolder, printer: printer)
// try generatedFolder.createSubfolderIfNeeded(withName: "Packages")
// } catch {
// throw Error.failedToUpdatePackages(folder)
// }
}

private func addMissingPackageFiles() {
Expand All @@ -335,7 +329,7 @@ public final class PackageManager {
let package = Package(
name: pinnedPackage.name,
url: pinnedPackage.url,
majorVersion: pinnedPackage.version.major
version: pinnedPackage.version
)

try save(package: package)
Expand Down Expand Up @@ -399,12 +393,69 @@ public final class PackageManager {
try generatedFolder.createFile(named: "Package.swift",
contents: description.data(using: .utf8).require())
}

private func generatePackageDescription(for script: Script, toolsVersion: Version) throws -> String{
let header = makePackageDescriptionHeader(forSwiftToolsVersion: toolsVersion)
let packages = makePackageDictionary()

var description = "\(header)\n\n" +
"import PackageDescription\n\n" +
"let package = Package(\n" +
" name: \"\(script.name)\",\n" +
" products: [],\n" +
" dependencies: [\n"

let dependencyPackages = try Set(script.dependencies.map { dependency throws -> Package in
guard let package = packages[dependency.url] else {
throw PackageManagerError.failedToReadPackageFile(dependency.url.absoluteString)
}
return package
})

for (index, package) in dependencyPackages.enumerated() {
if index > 0 {
description += ",\n"
}

let dependencyString = package.dependencyString(forSwiftToolsVersion: toolsVersion)
description.append(" \(dependencyString)")
}

description.append("\n ],\n")

if toolsVersion.major > 3 {
description.append(" targets: [.target(name: \"\(script.name)\", dependencies: [")

if !script.dependencies.isEmpty {
description.append("\"")
let dependencyNames = try script.dependencies.map { try $0.name ?? nameOfPackage(at: $0.url) }
description.append(dependencyNames.joined(separator: "\", \""))
description.append("\"")
}

description.append("])],\n")
}

if toolsVersion.major >= 4 && toolsVersion.minor >= 2 {
description.append(" swiftLanguageVersions: [.version(\"\(toolsVersion.major).\(toolsVersion.minor)\")]\n)")
} else {
description.append(" swiftLanguageVersions: [\(toolsVersion.major)]\n)")
}

return description
}

private func makePackageList() -> [Package] {
return folder.files.compactMap { file in
return try? unbox(data: file.read())
}
}

private func makePackageDictionary() -> [URL: Package] {
var packageDict: [URL: Package] = [:]
makePackageList().forEach { packageDict[$0.url] = $0 }
return packageDict
}

private func resolveSwiftToolsVersion() throws -> Version {
var versionString = try shellOutToSwiftCommand("package --version", printer: printer)
Expand Down
4 changes: 3 additions & 1 deletion Sources/MarathonCore/Script.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,18 @@ public final class Script {

public let name: String
public let folder: Folder
public var dependencies: [Dependency]

private let printer: Printer
private var copyLoopDispatchQueue: DispatchQueue?
private var localPath: String { return "Sources/\(name)/main.swift" }

// MARK: - Init

init(name: String, folder: Folder, printer: Printer) {
public init(name: String, folder: Folder, dependencies: [Dependency], printer: Printer) {
self.name = name
self.folder = folder
self.dependencies = dependencies
self.printer = printer
}

Expand Down
27 changes: 17 additions & 10 deletions Sources/MarathonCore/ScriptManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,16 @@ public final class ScriptManager {
private func script(from file: File) throws -> Script {
let identifier = scriptIdentifier(from: file.path)
let folder = try createFolderIfNeededForScript(withIdentifier: identifier, file: file)
let script = Script(name: file.nameExcludingExtension, folder: folder, printer: printer)

let script = Script(name: file.nameExcludingExtension, folder: folder, dependencies: [], printer: printer)
if let marathonFile = try script.resolveMarathonFile(fileName: config.dependencyFile) {
try packageManager.addPackagesIfNeeded(from: marathonFile.packageURLs)
let marathonFileDependencies: [Dependency] = marathonFile.packageURLs.map { Dependency(name: nil, url: $0) }
let resolvedDependencies = try packageManager.addPackagesIfNeeded(from: marathonFileDependencies)
try addDependencyScripts(fromMarathonFile: marathonFile, for: script)
script.dependencies += resolvedDependencies
}

try resolveInlineDependencies(from: file)
script.dependencies += try resolveInlineDependencies(from: file)

do {
let packageFile = try folder.createFile(named: "Package.swift")
Expand Down Expand Up @@ -248,7 +250,7 @@ public final class ScriptManager {
let cloneFiles = cloneFolder.makeFileSequence(recursive: true)

if cloneFiles.contains(where: { $0.name == "main.swift" }) {
return Script(name: packageName, folder: cloneFolder, printer: printer)
return Script(name: packageName, folder: cloneFolder, dependencies: [], printer: printer)
}
}

Expand Down Expand Up @@ -278,7 +280,7 @@ public final class ScriptManager {

private func createFolderIfNeededForScript(withIdentifier identifier: String, file: File) throws -> Folder {
let scriptFolder = try cacheFolder.createSubfolderIfNeeded(withName: identifier)
try packageManager.symlinkPackages(to: scriptFolder)
// try packageManager.symlinkPackages(to: scriptFolder)

if (try? scriptFolder.file(named: "OriginalFile")) == nil {
try scriptFolder.createSymlink(to: file.path, at: "OriginalFile", printer: printer)
Expand Down Expand Up @@ -310,9 +312,10 @@ public final class ScriptManager {
}
}

private func resolveInlineDependencies(from file: File) throws {
private func resolveInlineDependencies(from file: File) throws -> [Dependency] {
let lines = try file.readAsString().components(separatedBy: .newlines)
var packageURLs = [URL]()

var dependencies = [Dependency]()

for line in lines {
if line.hasPrefix("import ") {
Expand All @@ -322,21 +325,25 @@ public final class ScriptManager {
continue
}

let importName = components.first!
.replacingOccurrences(of: "import ", with: "")
.replacingOccurrences(of: "//", with: "")
.trimmingCharacters(in: .whitespaces)
let urlString = components.last!.trimmingCharacters(in: .whitespaces)

guard let url = URL(string: urlString) else {
throw Error.invalidInlineDependencyURL(urlString)
}

packageURLs.append(url)
dependencies.append(Dependency(name: importName, url: url))
} else if let firstCharacter = line.unicodeScalars.first {
guard !CharacterSet.alphanumerics.contains(firstCharacter) else {
break
}
}
}

try packageManager.addPackagesIfNeeded(from: packageURLs)
return try packageManager.addPackagesIfNeeded(from: dependencies)
}

private func makeManagedScriptPathList() -> [String] {
Expand Down