Initial commit
This commit is contained in:
commit
6874c53fdc
|
@ -0,0 +1,4 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Simon Kågedal Reimer
|
||||
|
||||
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.
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "AEXML",
|
||||
"repositoryURL": "https://github.com/tadija/AEXML",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "54bb8ea6fb693dd3f92a89e5fcc19e199fdeedd0",
|
||||
"version": "4.3.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "PathKit",
|
||||
"repositoryURL": "https://github.com/kylef/PathKit",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "e2f5be30e4c8f531c9c1e8765aa7b71c0a45d7a0",
|
||||
"version": "0.9.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Spectre",
|
||||
"repositoryURL": "https://github.com/kylef/Spectre.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "f14ff47f45642aa5703900980b014c2e9394b6e5",
|
||||
"version": "0.9.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftShell",
|
||||
"repositoryURL": "https://github.com/kareman/SwiftShell",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "beebe43c986d89ea5359ac3adcb42dac94e5e08a",
|
||||
"version": "4.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "xcodeproj",
|
||||
"repositoryURL": "https://github.com/tuist/xcodeproj.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "4d31d2be2532e9213d58cd4e0b15588c5dfae42d",
|
||||
"version": "6.5.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// swift-tools-version:4.2
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "xcodeproj-modify",
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/tuist/xcodeproj.git", from: "6.2.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "xcodeproj-modify",
|
||||
dependencies: ["xcodeproj"]),
|
||||
.testTarget(
|
||||
name: "xcodeproj-modifyTests",
|
||||
dependencies: ["xcodeproj-modify"]),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,25 @@
|
|||
# xcodeproj-modify
|
||||
|
||||
This microtool, despite its very generic name, currently only performs one very specific task: adds a Run Script phase to an Xcode project.
|
||||
|
||||
The use case is when you have an ephemeral Xcode project that is regenerated by Swift Package Manager and you want to add a Run Script phase to integrate SwiftLint.
|
||||
|
||||
## Usage
|
||||
|
||||
```shell
|
||||
$ xcodeproj-modify MyProject.xcodeproj add-run-script-phase MyTarget swiftlint
|
||||
```
|
||||
|
||||
This will edit your Xcode project `MyProject.xcodeproj` and add a Run Script phase to the `MyTarget` target that runs swiftlint. Since you specify the script as a command line parameter to the tool, it gets unwieldy if it's a lot of code, so in that case I'd recommend putting it in a script and pass _that_ as the code to add-run-script-phase.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Using Mint:
|
||||
|
||||
```shell
|
||||
$ mint install skagedal/xcodeproj-modify
|
||||
```
|
||||
|
||||
You may also add the tool as a SPM dependency in your Package.swift and then run it with `swift run xcodeproj-modify`.
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import Foundation
|
||||
|
||||
struct Arguments {
|
||||
let xcodeprojPath: String
|
||||
let commands: [Command]
|
||||
|
||||
enum Error: LocalizedError {
|
||||
case missingXcodeprojPath
|
||||
case missingCommand
|
||||
case unknownCommand(String)
|
||||
case missingTarget
|
||||
case missingContents
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .missingXcodeprojPath:
|
||||
return "Please give path to xcodeproj as first argument"
|
||||
case .missingCommand:
|
||||
return "Please give a command (add-run-script-phase)"
|
||||
case .unknownCommand(let command):
|
||||
return "Unknown command: \(command)"
|
||||
case .missingTarget:
|
||||
return "Please specify a target as the first argument after add-run-script-phase"
|
||||
case .missingContents:
|
||||
return "Please specify shell script contents as the second argument after add-run-script-phase"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private typealias TokenIterator = IndexingIterator<ArraySlice<String>>
|
||||
|
||||
init(_ arguments: [String]) throws {
|
||||
var iterator = arguments.dropFirst().makeIterator()
|
||||
|
||||
guard let path = iterator.next() else {
|
||||
throw Error.missingXcodeprojPath
|
||||
}
|
||||
xcodeprojPath = path
|
||||
|
||||
guard let commandName = iterator.next() else {
|
||||
throw Error.missingCommand
|
||||
}
|
||||
let command = try Arguments.parseCommand(named: commandName, from: &iterator)
|
||||
commands = [command]
|
||||
}
|
||||
|
||||
private static func parseCommand(named commandName: String, from iterator: inout TokenIterator) throws -> Command {
|
||||
switch commandName {
|
||||
case "add-run-script-phase":
|
||||
guard let target = iterator.next() else {
|
||||
throw Error.missingTarget
|
||||
}
|
||||
guard let contents = iterator.next() else {
|
||||
throw Error.missingContents
|
||||
}
|
||||
return Command.addRunScriptPhase(target: target, contents: contents)
|
||||
|
||||
default:
|
||||
throw Error.unknownCommand(commandName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import Foundation
|
||||
|
||||
enum Command {
|
||||
case addRunScriptPhase(target: String, contents: String)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import Foundation
|
||||
import PathKit
|
||||
import xcodeproj
|
||||
|
||||
struct XcodeprojModify {
|
||||
enum Error: LocalizedError {
|
||||
case unknownTarget(String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .unknownTarget(let string):
|
||||
return "Couldn't find target named \(string)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let arguments: [String]
|
||||
|
||||
init(arguments: [String]) {
|
||||
self.arguments = arguments
|
||||
}
|
||||
|
||||
public func run() -> Int32 {
|
||||
do {
|
||||
let arguments = try Arguments(self.arguments)
|
||||
try run(with: arguments)
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private func run(with arguments: Arguments) throws {
|
||||
for command in arguments.commands {
|
||||
try runCommand(command, with: arguments)
|
||||
}
|
||||
}
|
||||
|
||||
private func runCommand(_ command: Command, with arguments: Arguments) throws {
|
||||
switch command {
|
||||
case .addRunScriptPhase(let target, let contents):
|
||||
try addRunScriptPhase(target: target, contents: contents, xcodeprojPath: arguments.xcodeprojPath)
|
||||
}
|
||||
}
|
||||
|
||||
private func addRunScriptPhase(target: String, contents: String, xcodeprojPath: String) throws {
|
||||
let path = Path(xcodeprojPath)
|
||||
let xcodeproj = try XcodeProj(path: path)
|
||||
let targets = xcodeproj.pbxproj.targets(named: target)
|
||||
guard !targets.isEmpty else {
|
||||
throw Error.unknownTarget(target)
|
||||
}
|
||||
|
||||
let phase = PBXShellScriptBuildPhase(shellScript: contents)
|
||||
for target in targets {
|
||||
target.buildPhases = target.buildPhases + [phase]
|
||||
}
|
||||
try xcodeproj.write(path: path)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import Foundation
|
||||
|
||||
exit(XcodeprojModify(arguments: CommandLine.arguments).run())
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import XCTest
|
||||
|
||||
import xcodeproj_modifyTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += xcodeproj_modifyTests.allTests()
|
||||
XCTMain(tests)
|
|
@ -0,0 +1,9 @@
|
|||
import XCTest
|
||||
|
||||
#if !os(macOS)
|
||||
public func allTests() -> [XCTestCaseEntry] {
|
||||
return [
|
||||
testCase(xcodeproj_modifyTests.allTests),
|
||||
]
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,47 @@
|
|||
import XCTest
|
||||
import class Foundation.Bundle
|
||||
|
||||
final class xcodeproj_modifyTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
|
||||
// Some of the APIs that we use below are available in macOS 10.13 and above.
|
||||
guard #available(macOS 10.13, *) else {
|
||||
return
|
||||
}
|
||||
|
||||
let fooBinary = productsDirectory.appendingPathComponent("xcodeproj_modify")
|
||||
|
||||
let process = Process()
|
||||
process.executableURL = fooBinary
|
||||
|
||||
let pipe = Pipe()
|
||||
process.standardOutput = pipe
|
||||
|
||||
try process.run()
|
||||
process.waitUntilExit()
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let output = String(data: data, encoding: .utf8)
|
||||
|
||||
XCTAssertEqual(output, "Hello, world!\n")
|
||||
}
|
||||
|
||||
/// Returns path to the built products directory.
|
||||
var productsDirectory: URL {
|
||||
#if os(macOS)
|
||||
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
|
||||
return bundle.bundleURL.deletingLastPathComponent()
|
||||
}
|
||||
fatalError("couldn't find the products directory")
|
||||
#else
|
||||
return Bundle.main.bundleURL
|
||||
#endif
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testExample", testExample),
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue