-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Convert
import_indexstores.sh
into Swift binary
This is mainly to be able to do more complex stuff in the near future for incremental generators. It has the side benefit of being faster. Signed-off-by: Brentley Jones <[email protected]>
- Loading branch information
1 parent
cbc9042
commit aa7a7ee
Showing
17 changed files
with
439 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
load("@build_bazel_rules_apple//apple:apple.bzl", "apple_universal_binary") | ||
load( | ||
"@build_bazel_rules_apple//apple:macos.bzl", | ||
"macos_command_line_application", | ||
) | ||
load( | ||
"@build_bazel_rules_swift//swift:swift.bzl", | ||
"swift_binary", | ||
"swift_library", | ||
) | ||
|
||
# This target exists to keep configurations the same between the generator | ||
# and the tests, which makes the Xcode development experience better. If we used | ||
# `swift_binary` or `apple_universal_binary` in `xcodeproj`, then the | ||
# `macos_unit_test` transition (which is used to be able to set a minimum os | ||
# version on the tests) will create slightly different configurations for our | ||
# `swift_library`s. Maybe https://github.com/bazelbuild/bazel/issues/6526 will | ||
# fix that for us. | ||
macos_command_line_application( | ||
name = "import_indexstores", | ||
minimum_os_version = "12.0", | ||
visibility = ["//visibility:public"], | ||
deps = [":import_indexstores.library"], | ||
) | ||
|
||
swift_library( | ||
name = "import_indexstores.library", | ||
srcs = glob(["*.swift"]), | ||
module_name = "import_indexstores", | ||
) | ||
|
||
swift_binary( | ||
name = "import_indexstores_binary", | ||
deps = [":import_indexstores.library"], | ||
) | ||
|
||
apple_universal_binary( | ||
name = "universal_import_indexstores", | ||
binary = ":import_indexstores_binary", | ||
forced_cpus = [ | ||
"x86_64", | ||
"arm64", | ||
], | ||
minimum_os_version = "12.0", | ||
platform_type = "macos", | ||
visibility = ["//visibility:public"], | ||
) | ||
|
||
# Release | ||
|
||
filegroup( | ||
name = "release_files", | ||
srcs = [ | ||
"BUILD.release.bazel", | ||
":universal_import_indexstores", | ||
], | ||
tags = ["manual"], | ||
visibility = ["//:__subpackages__"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
exports_files(["universal_import_indexstores"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import Foundation | ||
|
||
/// An `Error` that represents a programming error. | ||
public struct PreconditionError: Error { | ||
public let message: String | ||
public let file: StaticString | ||
public let line: UInt | ||
|
||
public init( | ||
message: String, | ||
file: StaticString = #filePath, | ||
line: UInt = #line | ||
) { | ||
self.message = message | ||
self.file = file | ||
self.line = line | ||
} | ||
} | ||
|
||
extension PreconditionError: LocalizedError { | ||
public var errorDescription: String? { | ||
return """ | ||
Internal precondition failure: | ||
\(file):\(line): \(message) | ||
Please file a bug report at \ | ||
https://github.com/MobileNativeFoundation/rules_xcodeproj/issues/new?template=bug.md | ||
""" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import Foundation | ||
|
||
@main | ||
struct ImportIndex { | ||
static func main() async throws { | ||
let args = CommandLine.arguments | ||
let pidFile = | ||
try getEnvironmentVariable("OBJROOT") + "/import_indexstores.pid" | ||
|
||
guard args.count > 1 else { | ||
throw PreconditionError( | ||
message: "Not enough arguments, expected path to execution root" | ||
) | ||
} | ||
let buildExecutionRoot = args[1] | ||
|
||
// Exit early if no indexstore filelists were provided | ||
if args.count == 2 { | ||
return | ||
} | ||
|
||
// MARK: pidFile | ||
|
||
// Kill any previously running import | ||
if FileManager.default.fileExists(atPath: pidFile) { | ||
let pid = try String(contentsOfFile: pidFile) | ||
|
||
try runSubProcess("/bin/kill", [pid]) | ||
while true { | ||
if try runSubProcess("/bin/kill", ["-0", pid]) != 0 { | ||
break | ||
} | ||
sleep(1) | ||
} | ||
} | ||
|
||
// Set pid to allow cleanup later | ||
try String(ProcessInfo.processInfo.processIdentifier) | ||
.write(toFile: pidFile, atomically: true, encoding: .utf8) | ||
defer { | ||
try? FileManager.default.removeItem(atPath: pidFile) | ||
} | ||
|
||
// MARK: filelist | ||
|
||
let projectDirPrefix = try getEnvironmentVariable("PROJECT_DIR") + "/" | ||
|
||
// Merge all filelists into a single file | ||
var indexStores: Set<String> = [] | ||
for filePath in args.dropFirst(2) { | ||
let url = URL(fileURLWithPath: filePath) | ||
for try await indexStore in url.lines { | ||
indexStores.insert(indexStore) | ||
} | ||
} | ||
|
||
// Exit early if no indexstores were provided | ||
guard !indexStores.isEmpty else { | ||
return | ||
} | ||
|
||
let filelistContent = indexStores | ||
.map { projectDirPrefix + $0 + "\n" } | ||
.joined() | ||
let filelist = try TemporaryFile() | ||
try filelistContent | ||
.write(to: filelist.url, atomically: true, encoding: .utf8) | ||
|
||
// MARK: Remaps | ||
|
||
// We remove any `/private` prefix from the current execution_root, | ||
// since it's removed in the Project navigator | ||
let xcodeExecutionRoot: String | ||
if buildExecutionRoot.hasPrefix("/private") { | ||
xcodeExecutionRoot = String(buildExecutionRoot.dropFirst(8)) | ||
} else { | ||
xcodeExecutionRoot = buildExecutionRoot | ||
} | ||
|
||
let projectTempDir = try getEnvironmentVariable("PROJECT_TEMP_DIR") | ||
|
||
let objectFilePrefix: String | ||
if try getEnvironmentVariable("ACTION") == "indexbuild" { | ||
// Remove `Index.noindex/` part of path | ||
objectFilePrefix = projectTempDir.replacingOccurrences( | ||
of: "/Index.noindex/Build/Intermediates.noindex/", | ||
with: "/Build/Intermediates.noindex/" | ||
) | ||
} else { | ||
// Remove SwiftUI Previews part of path | ||
objectFilePrefix = try projectTempDir.replacingRegex( | ||
matching: #""" | ||
Intermediates\.noindex/Previews/[^/]*/Intermediates\.noindex | ||
"""#, | ||
with: "Intermediates.noindex" | ||
) | ||
} | ||
|
||
let xcodeOutputBase = xcodeExecutionRoot | ||
.split(separator: "/") | ||
.dropLast(2) | ||
.joined(separator: "/") | ||
|
||
let archs = try getEnvironmentVariable("ARCHS") | ||
let arch = String(archs.split(separator: " ", maxSplits: 1).first!) | ||
|
||
let remaps = remapArgs( | ||
arch: arch, | ||
developerDir: try getEnvironmentVariable("DEVELOPER_DIR"), | ||
objectFilePrefix: objectFilePrefix, | ||
srcRoot: try getEnvironmentVariable("SRCROOT"), | ||
xcodeExecutionRoot: xcodeExecutionRoot, | ||
xcodeOutputBase: xcodeOutputBase | ||
) | ||
|
||
// MARK: Import | ||
|
||
let indexDataStoreDir = URL( | ||
fileURLWithPath: try getEnvironmentVariable("INDEX_DATA_STORE_DIR") | ||
) | ||
let recordsDir = indexDataStoreDir.appendingPathComponent("v5/records") | ||
|
||
try FileManager.default.createDirectory( | ||
at: recordsDir, | ||
withIntermediateDirectories: true | ||
) | ||
|
||
try runSubProcess( | ||
try getEnvironmentVariable("INDEX_IMPORT"), | ||
remaps + [ | ||
"-undo-rules_swift-renames", | ||
"-incremental", | ||
"@\(filelist.url.path)", | ||
indexDataStoreDir.path, | ||
] | ||
) | ||
|
||
// Unit files are created fresh, but record files are copied from | ||
// `bazel-out/`, which are read-only. We need to adjust their | ||
// permissions. | ||
// TODO: do this in `index-import` | ||
try setWritePermissions(in: recordsDir) | ||
} | ||
} | ||
|
||
private func getEnvironmentVariable( | ||
_ key: String, | ||
file: StaticString = #filePath, | ||
line: UInt = #line | ||
) throws -> String { | ||
guard let value = ProcessInfo.processInfo.environment[key] else { | ||
throw PreconditionError( | ||
message: #"Environment variable "\#(key)" not set"#, | ||
file: file, | ||
line: line | ||
) | ||
} | ||
guard !value.isEmpty else { | ||
throw PreconditionError( | ||
message: #""" | ||
Environment variable "\#(key)" is set to an empty string | ||
"""#, | ||
file: file, | ||
line: line | ||
) | ||
} | ||
return value | ||
} | ||
|
||
@discardableResult private func runSubProcess( | ||
_ executable: String, | ||
_ args: [String] | ||
) throws -> Int32 { | ||
let task = Process() | ||
task.launchPath = executable | ||
task.arguments = args | ||
try task.run() | ||
task.waitUntilExit() | ||
return task.terminationStatus | ||
} | ||
|
||
private func setWritePermissions(in url: URL) throws { | ||
let enumerator = FileManager.default.enumerator( | ||
at: url, | ||
includingPropertiesForKeys: [.isDirectoryKey] | ||
)! | ||
for case let url as URL in enumerator { | ||
let resourceValues = try url.resourceValues(forKeys: [.isDirectoryKey]) | ||
if resourceValues.isDirectory! { | ||
try FileManager.default.setAttributes( | ||
[.posixPermissions: 0o755], | ||
ofItemAtPath: url.path | ||
) | ||
} else { | ||
try FileManager.default.setAttributes( | ||
[.posixPermissions: 0o644], | ||
ofItemAtPath: url.path | ||
) | ||
} | ||
} | ||
} | ||
|
||
extension String { | ||
func replacingRegex( | ||
matching pattern: String, | ||
with template: String | ||
) throws -> String { | ||
let regex = try NSRegularExpression(pattern: pattern) | ||
let range = NSRange(startIndex..., in: self) | ||
return regex.stringByReplacingMatches( | ||
in: self, | ||
range: range, | ||
withTemplate: template | ||
) | ||
} | ||
} |
Oops, something went wrong.