Commit a0087169 authored by Drew's avatar Drew

WIP: atpkg support for binary atpm

See https://github.com/AnarchyTools/atpm/issues/1

This commit adds

* Dependency type to ExternalDependency, to distinguish between Git and Manifest types
  This design was settled on after looking at several, including autodetecting git (which is possible via git ls-remote)
  However, manifest ExternalDependency is quite different than Git ExternalDependency for several reasons (including the use of channels)
* Channels, which are essentially like micropackages inside a package.  A channel lets us say "stable" "beta" "linux" "osx" "swift2.2" etc.  All releases are part of a channel.  It would be possible to use separate manifests for each channel, but this would require the use of multiple manifests for basic osx/linux packages, which is undesireable
* Tokens can now start with numbers, which is important, as we now have syntax where `:1.0 { :url "https://example.com/some/tarball.xz"}` is used to define where some version can be located
parent d089d258
Pipeline #2196 passed with stage
in 53 seconds
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import atfoundation
///A "channel" of releases, such as `stable`, `linux`, `osx-framework`, `swift3`, etc.
public struct BinaryChannel {
public let name: String
public let versions: [BinaryVersion]
init(name: String, versions: [BinaryVersion]) {
self.name = name
self.versions = versions
}
static func parse(_ pv: ParseValue) -> [BinaryChannel] {
var arr : [BinaryChannel] = []
guard case .Map(let map) = pv else { fatalError("Non-map binary channels")}
for (channel,cm) in map {
guard case .Map(let channelMap) = cm else {fatalError("Non-map for channel \(channel)")}
var channelArray: [BinaryVersion] = []
for(version, vm) in channelMap {
guard case .Map(let versionMap) = vm else { fatalError("Non-map for version \(channel).\(version)")}
guard let u = versionMap["url"] else {fatalError("No url for binary version \(version)")}
guard case .StringLiteral(let us) = u else { fatalError("Non-string value \(u) for url")}
let url = URL(string: us)
let version = BinaryVersion(version: version, url: url)
channelArray.append(version)
}
let bc = BinaryChannel(name: channel, versions: channelArray)
arr.append(bc)
}
return arr
}
}
///A pointer to an individual binary release tarball
public struct BinaryVersion {
public let version: String
public let url: URL
}
\ No newline at end of file
......@@ -21,38 +21,67 @@ final public class ExternalDependency {
case Branch(String)
case Tag(String)
}
public enum DependencyType {
case Git
case Manifest
}
public var gitURL: URL
public var url: URL
public var version: VersioningMethod
public var channels: [String]?
public var dependencyType: DependencyType
///atpm sets this value when it parses the name from the manifest.
///This value is then returned from `name` on request.
///Therefore, we "learn" the value of a remote package name after parsing its manifest.
///- warning: This API is particular to atpm, it is probably not useful unless you are working on that project
public var _parsedNameFromManifest: String? = nil
///Custom info available for use by the application.
///In practice, this is used to hold lock information for atpm
public var _applicationInfo: Any? = nil
public var name: String {
if let lastComponent = gitURL.path.components.last {
///The name of the dependency.
///Note that if the dependency points to a manifest, the name is not known.
public var name: String? {
if self.dependencyType == .Manifest {
if let p = _parsedNameFromManifest { return p }
return nil
}
if let lastComponent = url.path.components.last {
if lastComponent.hasSuffix(".git") {
return lastComponent.subString(toIndex: lastComponent.index(lastComponent.endIndex, offsetBy: -4))
}
return lastComponent
} else {
return "unknown"
return nil
}
}
init?(url: String, version: [String]) {
self.gitURL = URL(string: url)
self.version = .Version(version)
private init?(url: String, versionMethod: VersioningMethod, channels: [String]?) {
self.url = URL(string: url)
self.version = versionMethod
self.channels = channels
if url.hasSuffix(".atpkg") {
self.dependencyType = .Manifest
}
else { self.dependencyType = .Git }
print("dependency type \(self.dependencyType)")
}
convenience init?(url: String, version: [String], channels: [String]?) {
self.init(url: url, versionMethod: .Version(version), channels: channels)
}
init?(url: String, commit: String) {
self.gitURL = URL(string: url)
self.version = .Commit(commit)
convenience init?(url: String, commit: String, channels: [String]?) {
self.init(url: url, versionMethod: .Commit(commit), channels: channels)
}
init?(url: String, branch: String) {
self.gitURL = URL(string: url)
self.version = .Branch(branch)
convenience init?(url: String, branch: String, channels: [String]?) {
self.init(url: url, versionMethod: .Branch(branch), channels: channels)
}
init?(url: String, tag: String) {
self.gitURL = URL(string: url)
self.version = .Tag(tag)
convenience init?(url: String, tag: String, channels: [String]?) {
self.init(url: url, versionMethod: .Tag(tag), channels: channels)
}
}
\ No newline at end of file
......@@ -86,6 +86,8 @@ final public class Package {
case Overlays = "overlays"
case UseOverlays = "use-overlays"
case Payload = "payload"
case Binaries = "binaries"
case BinaryChannels = "channels"
static var allKeys: [Key] {
return [
......@@ -97,7 +99,9 @@ final public class Package {
Tasks,
Overlays,
UseOverlays,
Payload
Binaries,
BinaryChannels,
Payload
]
}
}
......@@ -125,6 +129,8 @@ final public class Package {
///Overlays that are an (indirect) child of the receiver. these are indexed by qualified name.
private var importedOverlays: [String: [String: ParseValue]] = [:]
public var binaryChannels : [BinaryChannel]?
///The union of childOverlays and importedOverlays
var overlays : [String: [String: ParseValue]] {
var arr = childOverlays
......@@ -229,6 +235,17 @@ final public class Package {
for dep in externalDeps {
guard let d = dep.map else { fatalError("Non-Map external dependency declaration") }
guard let url = d["url"]?.string else { fatalError("No URL in dependency declaration") }
let channels: [String]?
if let c = d["channels"] {
var channels_ : [String] = []
guard case .Vector(let v) = c else { fatalError("Non-vector channel specification")}
for cs in v {
guard case .StringLiteral(let s) = cs else { fatalError("Non-string channel specifier \(cs)")}
channels_.append(s)
}
channels = channels_
}
else { channels = nil }
var externalDep: ExternalDependency? = nil
if let version = d["version"]?.vector {
var versionDecl = [String]()
......@@ -239,28 +256,37 @@ final public class Package {
fatalError("Could not parse external dependency version declaration for \(url)")
}
}
externalDep = ExternalDependency(url: url, version: versionDecl)
externalDep = ExternalDependency(url: url, version: versionDecl, channels: channels)
} else if let branch = d["branch"]?.string {
externalDep = ExternalDependency(url: url, branch: branch)
externalDep = ExternalDependency(url: url, branch: branch, channels: channels)
} else if let commit = d["commit"]?.string {
externalDep = ExternalDependency(url: url, commit: commit)
externalDep = ExternalDependency(url: url, commit: commit, channels: channels)
} else if let tag = d["tag"]?.string {
externalDep = ExternalDependency(url: url, tag: tag)
externalDep = ExternalDependency(url: url, tag: tag, channels: channels)
}
if let externalDep = externalDep {
// add to external deps
self.externals.append(externalDep)
let importFileString = "external/" + externalDep.name + "/build.atpkg"
// import the atbuild file if it is there
let adjustedImportPath = (pathOnDisk + importFileString).dirname()
do {
let remotePackage = try Package(filepath: pathOnDisk + importFileString, overlay: requestedGlobalOverlays, focusOnTask: nil)
remotePackage.adjustedImportPath = adjustedImportPath
remotePackages.append(remotePackage)
} catch {
print("Unsatisfied external dependency: \(externalDep.name) (Error: \(error)), run atpm fetch")
switch(externalDep.dependencyType) {
case .Git:
let importFileString = "external/" + externalDep.name! + "/build.atpkg"
// import the atbuild file if it is there
let adjustedImportPath = (pathOnDisk + importFileString).dirname()
do {
let remotePackage = try Package(filepath: pathOnDisk + importFileString, overlay: requestedGlobalOverlays, focusOnTask: nil)
remotePackage.adjustedImportPath = adjustedImportPath
remotePackages.append(remotePackage)
} catch {
print("Unsatisfied external dependency: \(externalDep.name!) (Error: \(error)), run atpm fetch")
}
case .Manifest:
//we don't import the package in this case
break
}
} else {
fatalError("Could not parse external dependency declaration for \(url)")
}
......@@ -350,5 +376,11 @@ final public class Package {
self.tasks[task.qualifiedName] = task
}
}
//load binary channels
if let binaries = type.properties[Key.Binaries.rawValue]?.map, let channels = binaries[Key.BinaryChannels.rawValue] {
self.binaryChannels = BinaryChannel.parse(channels)
}
else { self.binaryChannels = nil }
}
}
\ No newline at end of file
......@@ -56,14 +56,14 @@ func isValidIdentifierSignalCharacter(c: Character?) -> Bool {
guard let c = c else {
return false
}
return Charset.isLetter(character: c)
return Charset.isLetter(character: c) || Charset.isNumberDigit(character: c)
}
func isValidIdenitifierCharacter(c: Character?) -> Bool {
guard let c = c else {
return false
}
return Charset.isLetter(character: c) || c == "-" || c == "." || c == "/"
return Charset.isLetter(character: c) || Charset.isNumberDigit(character: c) || c == "-" || c == "." || c == "/"
}
func isWhitespace(c: Character?) -> Bool {
......
(package
:name "DummyBinary"
:binaries {
:channels {
:linux {
:1.0 {
:url "https://github.com/AnarchyTools/dummyBinaryPackage/releases/download/0.1/linux.tar.xz"
}
}
:osx {
:1.0 {
:url "https://github.com/AnarchyTools/dummyBinaryPackage/releases/download/0.1/osx.tar.xz"
}
}
}
}
)
\ No newline at end of file
;; Copyright (c) 2016 Anarchy Tools Contributors.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at
;;
;; http:;;www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.
(package
:name "use-binary"
:external-packages [
{
:url "https://raw.githubusercontent.com/AnarchyTools/dummyBinaryPackage/master/manifest.atpkg"
:version [">=1.0"]
:channels ["osx"]
}
]
)
......@@ -28,7 +28,9 @@ class PackageTests: Test {
PackageTests.testChainedImportOverlays,
PackageTests.nonVectorImport,
PackageTests.testRequireOverlays,
PackageTests.testOnlyPlatforms
PackageTests.testOnlyPlatforms,
PackageTests.testUseBinary,
PackageTests.testBinaryManifest,
]
......@@ -253,4 +255,31 @@ class PackageTests: Test {
guard let task = p.tasks["build"] else { fatalError("No build task")}
try test.assert(task.onlyPlatforms == ["linux","osx"])
}
static func testUseBinary() throws {
let filepath = Path("tests/collateral/use-binary.atpkg")
let p = try Package(filepath: filepath, overlay: [], focusOnTask: nil)
let dep = p.externals[0]
guard let channels = dep.channels else { fatalError("No channels ")}
try test.assert(channels == ["osx"])
}
static func testBinaryManifest() throws {
let filepath = Path("tests/collateral/binary-manifest.atpkg")
let p = try Package(filepath: filepath, overlay: [], focusOnTask: nil)
try test.assert(p.binaryChannels?.count == 2)
guard let channels = p.binaryChannels else {fatalError("No channels")}
for channel in channels {
try test.assert(channel.versions.count == 1)
if channel.name == "linux" {
try test.assert(channel.versions[0].url == URL(string: "https://github.com/AnarchyTools/dummyBinaryPackage/releases/download/0.1/linux.tar.xz"))
}
else {
try test.assert(channel.versions[0].url == URL(string: "https://github.com/AnarchyTools/dummyBinaryPackage/releases/download/0.1/osx.tar.xz"))
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment