-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Xcodeproj] Add support for automatic project generation #1604
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -172,6 +172,15 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> { | |
|
||
print("generated:", xcodeprojPath.prettyPath(cwd: originalWorkingDirectory)) | ||
|
||
// Run the file watcher if requested. | ||
if options.xcodeprojOptions.enableAutogeneration { | ||
try WatchmanHelper( | ||
diagnostics: diagnostics, | ||
watchmanScriptsDir: buildPath.appending(component: "watchman"), | ||
packageRoot: packageRoot! | ||
).runXcodeprojWatcher(options.xcodeprojOptions) | ||
} | ||
|
||
case .describe: | ||
let graph = try loadPackageGraph() | ||
describe(graph.rootPackages[0].underlyingPackage, in: options.describeMode, on: stdoutStream) | ||
|
@@ -314,11 +323,15 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> { | |
$0.outputPath = $3?.path | ||
}) | ||
binder.bind( | ||
option: generateXcodeParser.add( | ||
generateXcodeParser.add( | ||
option: "--legacy-scheme-generator", kind: Bool.self, | ||
usage: "Use the legacy scheme generator"), | ||
generateXcodeParser.add( | ||
option: "--watch", kind: Bool.self, | ||
usage: "Watch the filesystem and autogenerate the Xcode project if needed"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be worth being more specific?
|
||
to: { | ||
$0.xcodeprojOptions.useLegacySchemeGenerator = $1 | ||
$0.xcodeprojOptions.useLegacySchemeGenerator = $1 ?? false | ||
$0.xcodeprojOptions.enableAutogeneration = $2 ?? false | ||
}) | ||
|
||
let completionToolParser = parser.add( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/* | ||
This source file is part of the Swift.org open source project | ||
|
||
Copyright (c) 2018 Apple Inc. and the Swift project authors | ||
Licensed under Apache License v2.0 with Runtime Library Exception | ||
|
||
See http://swift.org/LICENSE.txt for license information | ||
See http://swift.org/CONTRIBUTORS.txt for Swift project authors | ||
*/ | ||
|
||
import Basic | ||
import Utility | ||
import POSIX | ||
import Xcodeproj | ||
|
||
struct WatchmanMissingDiagnostic: DiagnosticData { | ||
static let id = DiagnosticID( | ||
type: WatchmanMissingDiagnostic.self, | ||
name: "org.swift.diags.watchman-missing", | ||
description: { | ||
$0 <<< "this feature requires 'watchman' to work" | ||
$0 <<< "\n\n installation instructions for 'watchman' are available at https://facebook.github.io/watchman/docs/install.html#buildinstall" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the double-newlines? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks a bit "pretty". |
||
} | ||
) | ||
} | ||
|
||
final class WatchmanHelper { | ||
|
||
/// Name of the watchman-make tool. | ||
static let watchmanMakeTool: String = "watchman-make" | ||
|
||
/// Directory where watchman script should be created. | ||
let watchmanScriptsDir: AbsolutePath | ||
|
||
/// The package root. | ||
let packageRoot: AbsolutePath | ||
|
||
/// The filesystem to operator on. | ||
let fs: FileSystem | ||
|
||
let diagnostics: DiagnosticsEngine | ||
|
||
init( | ||
diagnostics: DiagnosticsEngine, | ||
watchmanScriptsDir: AbsolutePath, | ||
packageRoot: AbsolutePath, | ||
fs: FileSystem = localFileSystem | ||
) { | ||
self.watchmanScriptsDir = watchmanScriptsDir | ||
self.diagnostics = diagnostics | ||
self.packageRoot = packageRoot | ||
self.fs = fs | ||
} | ||
|
||
func runXcodeprojWatcher(_ options: XcodeprojOptions) throws { | ||
let scriptPath = try createXcodegenScript(options) | ||
try run(scriptPath) | ||
} | ||
|
||
func createXcodegenScript(_ options: XcodeprojOptions) throws -> AbsolutePath { | ||
let scriptPath = watchmanScriptsDir.appending(component: "gen-xcodeproj.sh") | ||
|
||
let stream = BufferedOutputByteStream() | ||
stream <<< "#!/usr/bin/env bash" <<< "\n\n\n" | ||
stream <<< "# Autogenerated by SwiftPM. Do not edit!" <<< "\n\n\n" | ||
stream <<< "set -eu" <<< "\n\n" | ||
stream <<< "swift package generate-xcodeproj" | ||
if let xcconfigOverrides = options.xcconfigOverrides { | ||
stream <<< " --xcconfig-overrides " <<< xcconfigOverrides.asString | ||
} | ||
stream <<< "\n" | ||
|
||
try fs.createDirectory(scriptPath.parentDirectory, recursive: true) | ||
try fs.writeFileContents(scriptPath, bytes: stream.bytes) | ||
try fs.chmod(.executable, path: scriptPath) | ||
|
||
return scriptPath | ||
} | ||
|
||
private func run(_ scriptPath: AbsolutePath) throws { | ||
// Construct the arugments. | ||
var args = [String]() | ||
args += ["--settle", "2"] | ||
args += ["-p", "Package.swift", "Package.resolved"] | ||
args += ["--run", scriptPath.asString.shellEscaped()] | ||
|
||
// Find and execute watchman. | ||
let watchmanMakeToolPath = try self.watchmanMakeToolPath() | ||
|
||
print("Starting:", watchmanMakeToolPath.asString, args.joined(separator: " ")) | ||
|
||
let pathRelativeToWorkingDirectory = watchmanMakeToolPath.relative(to: packageRoot) | ||
try exec(path: watchmanMakeToolPath.asString, args: [pathRelativeToWorkingDirectory.asString] + args) | ||
} | ||
|
||
private func watchmanMakeToolPath() throws -> AbsolutePath { | ||
if let toolPath = Process.findExecutable(WatchmanHelper.watchmanMakeTool) { | ||
return toolPath | ||
} | ||
diagnostics.emit(data: WatchmanMissingDiagnostic()) | ||
throw Error.hasFatalDiagnostics | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,8 @@ import XCTest | |
import Foundation | ||
|
||
import Basic | ||
import Commands | ||
@testable import Commands | ||
import Xcodeproj | ||
import PackageModel | ||
import SourceControl | ||
import TestSupport | ||
|
@@ -489,6 +490,37 @@ final class PackageToolTests: XCTestCase { | |
} | ||
} | ||
|
||
func testWatchmanXcodeprojgen() { | ||
mktmpdir { path in | ||
let fs = localFileSystem | ||
let diagnostics = DiagnosticsEngine() | ||
|
||
let scriptsDir = path.appending(component: "scripts") | ||
let packageRoot = path.appending(component: "root") | ||
|
||
let helper = WatchmanHelper( | ||
diagnostics: diagnostics, | ||
watchmanScriptsDir: scriptsDir, | ||
packageRoot: packageRoot) | ||
|
||
let script = try helper.createXcodegenScript( | ||
XcodeprojOptions()) | ||
|
||
XCTAssertEqual(try fs.readFileContents(script), """ | ||
#!/usr/bin/env bash | ||
|
||
|
||
# Autogenerated by SwiftPM. Do not edit! | ||
|
||
|
||
set -eu | ||
|
||
swift package generate-xcodeproj | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we also want a test that makes such that we pass |
||
|
||
""") | ||
} | ||
} | ||
|
||
static var allTests = [ | ||
("testDescribe", testDescribe), | ||
("testUsage", testUsage), | ||
|
@@ -507,5 +539,6 @@ final class PackageToolTests: XCTestCase { | |
("testPinning", testPinning), | ||
("testPinningBranchAndRevision", testPinningBranchAndRevision), | ||
("testSymlinkedDependency", testSymlinkedDependency), | ||
("testWatchmanXcodeprojgen", testWatchmanXcodeprojgen), | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want to update to documentation to say a path is returned.