Skip to content

Commit

Permalink
Documentation improvements, BundleVersion -> Version, Version improve…
Browse files Browse the repository at this point in the history
…ments

Version's protocol conformance is now correct by not taking rawValue into account
  • Loading branch information
Josh Kaplan committed Oct 13, 2021
1 parent 12a1c70 commit ea97dfc
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 78 deletions.
38 changes: 18 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
Using this framework you can read property lists embedded inside of a running application as well as those of
executables stored on disk. Built in support is provided for reading both embedded info and launchd property lists.
Custom property list types can also be specified.
Using this framework you can read property lists embedded inside of a running executable as well as those of
executables stored on disk. These types of executables are often Command Line Tools. Built-in support is provided for
reading both embedded info and launchd property lists. Custom property list types can also be specified.

# Usage
Property lists are returned as [`Data`](https://developer.apple.com/documentation/foundation/data) instances. In most
cases you'll want to deserialize using one of:
Property lists are returned as [`Data`](https://developer.apple.com/documentation/foundation/data) instances. Usually
you'll want to deserialize using one of:
* `ProperyListDecoder`'s
[`decode(_:from:)`](https://developer.apple.com/documentation/foundation/propertylistdecoder/2895397-decode)
function to decode the `Data` into a [`Decodable`](https://developer.apple.com/documentation/swift/decodable)
you define
to deserialize the `Data` into a [`Decodable`](https://developer.apple.com/documentation/swift/decodable)
* `PropertyListSerialization`'s
[`propertyList(from:options:format:)`](https://developer.apple.com/documentation/foundation/propertylistserialization/1409678-propertylist)
function to decode the `Data` into an
[`NSDictionary`](https://developer.apple.com/documentation/foundation/nsdictionary) containing the contents of the
property list
to deserialize the `Data` into an [`NSDictionary`](https://developer.apple.com/documentation/foundation/nsdictionary)

### Example — Read Internal, Create `Decodable`
Decode a launchd property list when running inside an executable into a custom `Decodable` struct:
When running inside an executable, decode a launchd property list into a custom `Decodable` struct:
```swift
struct LaunchdPropertyList: Decodable {
public let machServices: [String : Bool]
public let label: String
let machServices: [String : Bool]
let label: String

private enum CodingKeys: CodingKey, String {
case machServices = "MachServices"
Expand All @@ -33,25 +30,26 @@ let plist = try PropertyListDecoder().decode(LaunchdPropertyList.self, from: dat
```

### Example — Read External, Create `NSDictionary`
Decode an info property list in an external executable into an `NSDictionary`:
For an external executable, deserialize an info property list as an `NSDictionary`:
```swift
let executableURL = URL(fileUrlWithPath: <# path here #>)
let data = try EmbeddedPropertyListReader.info.readExternal(from: executableURL)
let plist = try PropertyListSerialization.propertyList(from: data, format: nil) as? NSDictionary
let plist = try PropertyListSerialization.propertyList(from: data,
format: nil) as? NSDictionary
```

### Example — Create `Decodable` Using `BundleVersion`
Decode an info property list, using `BundleVersion` to decode the
### Example — Create `Decodable` Using `Version`
Decode an info property list, using `Version` to decode the
[`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion)
entry:

```swift
struct InfoPropertyList: Decodable {
public let version: BundleVersion
public let bundleIdentifier: String
let bundleVersion: Version
let bundleIdentifier: String

private enum CodingKeys: CodingKey, String {
case version = "CFBundleVersion"
case bundleVersion = "CFBundleVersion"
case bundleIdentifier = "CFBundleIdentifier"
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
# ``EmbeddedPropertyList``

Read property lists embedded inside of Mach-O executables (also known as a Command Line Tools).
Read property lists embedded inside of Mach-O executables.

## Overview
Using this framework you can read property lists embedded inside of a running application as well as those of
executables stored on disk. Built in support is provided for reading both embedded Info and launchd property lists.
Custom property list types can also be specified.
Using this framework you can read property lists embedded inside of a running executable as well as those of
executables stored on disk. These types of executables are often Command Line Tools. Built-in support is provided for
reading both embedded info and launchd property lists. Custom property list types can also be specified.

Property lists are returned as [`Data`](https://developer.apple.com/documentation/foundation/data) instances. In most
cases you'll want to deserialize using one of:
> Note: Only 64-bit Intel and ARM executables (or universal binary slices) are supported. Mac OS X 10.6
Snow Leopard was the last 32-bit OS. macOS 10.14 Mojave was the last to run 32-bit binaries.

## Usage
Property lists are returned as [`Data`](https://developer.apple.com/documentation/foundation/data) instances. Usually
you'll want to deserialize using one of:
* `ProperyListDecoder`'s
[`decode(_:from:)`](https://developer.apple.com/documentation/foundation/propertylistdecoder/2895397-decode)
function to decode the `Data` into a [`Decodable`](https://developer.apple.com/documentation/swift/decodable)
you define
to deserialize the `Data` into a [`Decodable`](https://developer.apple.com/documentation/swift/decodable)
* `PropertyListSerialization`'s
[`propertyList(from:options:format:)`](https://developer.apple.com/documentation/foundation/propertylistserialization/1409678-propertylist)
function to decode the `Data` into an
[`NSDictionary`](https://developer.apple.com/documentation/foundation/nsdictionary) containing the contents of the
property list
to deserialize the `Data` into an [`NSDictionary`](https://developer.apple.com/documentation/foundation/nsdictionary)

#### Example — Read Internal, Create Decodable
Decode a launchd property list when running inside an executable into a custom `Decodable` struct:
When running inside an executable, decode a launchd property list into a custom `Decodable` struct:
```swift
struct LaunchdPropertyList: Decodable {
public let machServices: [String : Bool]
public let label: String
let machServices: [String : Bool]
let label: String

private enum CodingKeys: CodingKey, String {
case machServices = "MachServices"
Expand All @@ -33,31 +34,31 @@ struct LaunchdPropertyList: Decodable {
}

let data = try EmbeddedPropertyListReader.launchd.readInternal()
let plist = try PropertyListDecoder().decode(LaunchdPropertyList.self,
from: data)
let plist = try PropertyListDecoder().decode(LaunchdPropertyList.self,
from: data)
```

#### Example — Read External, Create NSDictionary
Decode an info property list in an external executable into an `NSDictionary`:
For an external executable, deserialize an info property list as an `NSDictionary`:
```swift
let executableURL = URL(fileUrlWithPath: <# path here #>)
let data = try EmbeddedPropertyListReader.info.readExternal(from: executableURL)
let plist = try PropertyListSerialization.propertyList(from: data,
format: nil) as? NSDictionary
```

#### Example — Create Decodable Using BundleVersion
Decode an info property list, using ``BundleVersion`` to decode the
#### Example — Create Decodable Using Version
Decode an info property list, using ``Version`` to decode the
[`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion)
entry:

```swift
struct InfoPropertyList: Decodable {
public let version: BundleVersion
public let bundleIdentifier: String
let bundleVersion: Version
let bundleIdentifier: String

private enum CodingKeys: CodingKey, String {
case version = "CFBundleVersion"
case bundleVersion = "CFBundleVersion"
case bundleIdentifier = "CFBundleIdentifier"
}
}
Expand All @@ -68,13 +69,10 @@ let plist = try PropertyListDecoder().decode(InfoPropertyList.self, from: data)

#### Comparing Property Lists
In some circumstances you may want to directly compare two property lists. If you want to compare their true on disk
representations, you can
[compare them as `Data` instances](https://developer.apple.com/documentation/foundation/data/2293245). However, because
there are multiple encoding formats for property lists in many cases you should first decode them before performing a
comparison.

> Note: Only 64-bit Intel and ARM executables (or equivalent slices of universal binaries) are supported. Mac OS X 10.6
Snow Leopard was the last version to be 32-bit and macOS 10.14 Mojave was the last version to run 32-bit binaries.
representations, you can
[compare them as `Data` instances](https://developer.apple.com/documentation/foundation/data/2293245). However, because
there are multiple encoding formats for property lists in most cases you should first deserialize them before performing
a comparison.

## Topics

Expand All @@ -84,7 +82,7 @@ Snow Leopard was the last version to be 32-bit and macOS 10.14 Mojave was the la

### Property List Types

- ``BundleVersion``
- ``Version``

### Errors

Expand Down
15 changes: 5 additions & 10 deletions Sources/EmbeddedPropertyList/EmbeddedPropertyListReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

/// Read a property list embedded in a single file executable (also known as a Command Line Tool).
/// Read a property list embedded in a Mach-O executable.
public enum EmbeddedPropertyListReader {
/// An embedded info property list.
case info
Expand Down Expand Up @@ -36,8 +36,6 @@ public enum EmbeddedPropertyListReader {

/// Read the property list embedded within this executable.
///
/// > Warning: Due to unsafe memory usage, calling this function has the potential to result in bad memory access instead of throwing an error.
///
/// - Returns: The property list as data.
public func readInternal() throws -> Data {
// By passing in nil, this returns a handle for the dynamic shared object (shared library) for this executable
Expand All @@ -61,9 +59,10 @@ public enum EmbeddedPropertyListReader {
}
}

/// Read the property list embedded in the specified executable
/// Read the property list embedded in an on disk executable.
///
/// > Warning: Due to unsafe memory usage, calling this function has the potential to result in bad memory access instead of throwing an error.
/// If this is a universal binary and multiple architecture slices can be read, then the property list for one of the architectures will be returned. Which
/// architecture's property list is returned is undefined. However, in practice a given property list is likely to be identical across architectures.
///
/// - Parameters:
/// - from: Location of the executable to be read.
Expand All @@ -86,16 +85,14 @@ public enum EmbeddedPropertyListReader {
if isMagicFat(magic: magic) {
let mustSwap = mustSwapEndianness(magic: magic)
let offsets = machHeaderOffsetsForFatExecutable(data: data, mustSwap: mustSwap)
if let offset = offsets.first?.value { // TODO: better way to decide which slice to choose
if let offset = offsets.first?.value {
machHeaderOffset = offset
} else {
throw ReadError.unsupportedArchitecture
}
} else {
if !isMagic64(magic: magic) {
// This implementation only supports 64-bit architectures.
// Mac OS X 10.6 Snow Leopard was the last version to be 32-bit
// macOS 10.14 Mojave was the last version to run 32-bit binaries
throw ReadError.unsupportedArchitecture
}

Expand Down Expand Up @@ -223,8 +220,6 @@ public enum EmbeddedPropertyListReader {
}

// This implementation only supports 64-bit architectures.
// Mac OS X 10.6 Snow Leopard was the last version to be 32-bit
// macOS 10.14 Mojave was the last version to run 32-bit executables
if isMagic64(magic: readMagic(data: data, offset: arch.offset)) {
archOffsets[arch.cputype] = arch.offset
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/EmbeddedPropertyList/ReadError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

import Foundation

/// Errors that may occur while try to read embedded property lists.
/// Errors that may occur while trying to read embedded property lists.
public enum ReadError: Error {
/// The `__TEXT` section within the embedded property list that describes where the property list is stored was not found.
/// The `__TEXT` section describing where the property list is stored was not found in the Mach-O header.
case sectionNotFound
/// The provided URL does not reference an executable with a Mach-O header.
/// The file is not an executable with a Mach-O header.
case notMachOExecutable
/// None of the Mach-O header(s) ins the executable are supported.
/// None of the Mach-O header architectures in the executable are supported.
///
/// `x86_64` (Intel) and `arm64` (Apple Silicon) are supported.
/// `x86_64` (Intel) and `arm64` (Apple Silicon) architectures are supported.
case unsupportedArchitecture
/// The mach header execute symbol for the Mach-O executable could not be retrieved.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,46 @@

import Foundation

/// Represents a
/// [`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion)
/// value typically found in an Info.plist.
/// Represent a version as is typically included in an info property list.
///
/// Capable of representing any properly formatted version that matches one of:
/// Capable of representing any version with a format that matches one of:
/// - `major`
/// - `major.minor`
/// - `major.minor.patch`
///
/// Where `major`, `minor`, and `patch` must be `Int`s.
/// `major`, `minor`, and `patch` must be `Int`s. Any values not provided will be represented as `0`. For example if this represents `6.4` then `patch`
/// will be `0`. This matches
/// [`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion)
/// semantics.
///
/// > Note: While this key is called `CFBundleVersion` it does not exclusively represent a **bundle's** version, it will typically also be present in the Info.plist
/// embedded in a Mach-O executable.
public struct BundleVersion: Comparable, Decodable, Hashable, RawRepresentable, CustomStringConvertible {
/// > Note: `CFBundleVersion` does not exclusively represent a **bundle's** version. A Mach-O executable's info property list often contains this key.
public struct Version: Comparable, Decodable, Hashable, RawRepresentable, CustomStringConvertible {

public typealias RawValue = String

/// The raw string representation of the version.
/// The raw string representation of this version.
public let rawValue: String

/// The major version.
public let major: Int

/// The minor version.
///
/// `0` if not specified.
public let minor: Int

/// The patch version.
///
/// `0` if not specified.
public let patch: Int

/// Initializes from an encoded representation.
///
/// - Parameter decoder: <#decoder description#>
/// - Parameter decoder:
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let rawValue = try container.decode(String.self)
if let bundleVersion = BundleVersion(rawValue: rawValue) {
if let bundleVersion = Version(rawValue: rawValue) {
self = bundleVersion
} else {
let context = DecodingError.Context(codingPath: container.codingPath,
Expand Down Expand Up @@ -92,11 +96,13 @@ public struct BundleVersion: Comparable, Decodable, Hashable, RawRepresentable,
}
}

/// A textual representation of this version.
public var description: String {
return rawValue
return "\(self.major).\(self.minor).\(self.patch)"
}

public static func < (lhs: BundleVersion, rhs: BundleVersion) -> Bool {
/// Semantically compares two `Version` instances.
public static func < (lhs: Version, rhs: Version) -> Bool {
var lessThan = false
if lhs.major < rhs.major {
lessThan = true
Expand All @@ -113,4 +119,21 @@ public struct BundleVersion: Comparable, Decodable, Hashable, RawRepresentable,

return lessThan
}

/// Determines equality of two `Version` instances.
///
///
/// The ``rawValue-swift.property`` is not considered, meaning `6.4` and `6.4.0` are intentionally considered equal.
public static func == (lhs: Version, rhs: Version) -> Bool {
return (lhs.major == rhs.major) && (lhs.minor == rhs.minor) && (lhs.patch == rhs.patch)
}

/// Hashes this version.
///
/// Hashing does not take ``rawValue-swift.property`` into account, so `6.4` and `6.4.0` will intentionally hash to the same value.
public func hash(into hasher: inout Hasher) {
hasher.combine(self.major)
hasher.combine(self.minor)
hasher.combine(self.patch)
}
}

0 comments on commit ea97dfc

Please sign in to comment.