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