Skip to content
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

Convert import_indexstores.sh into a Swift binary #2671

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions distribution/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pkg_tar(
remap_paths = dicts.add(
{
"MODULE.release.bazel": "MODULE.bazel",
"tools/import_indexstores/BUILD.release.bazel": (
"tools/import_indexstores/BUILD"
),
"tools/swiftc_stub/BUILD.release.bazel": "tools/swiftc_stub/BUILD",
"xcodeproj/repositories.release.bzl": "xcodeproj/repositories.bzl",
},
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
20 changes: 20 additions & 0 deletions tools/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ load("//xcodeproj/internal:collections.bzl", "flatten", "uniq")

_TOOLS = {
"files_and_groups": "//tools/generators/files_and_groups",
"import_indexstores": "//tools/import_indexstores",
"pbxproj_prefix": "//tools/generators/pbxproj_prefix",
"pbxtargetdependencies": "//tools/generators/pbxtargetdependencies",
"swiftc_stub": "//tools/swiftc_stub:swiftc",
Expand Down Expand Up @@ -75,6 +76,24 @@ _SCHEMES = [
diagnostics = _SCHEME_DIAGNOSTICS,
),
),
xcode_schemes.scheme(
name = "import_indexstores",
build_action = xcode_schemes.build_action(
targets = [
xcode_schemes.build_target(
_TOOLS["import_indexstores"],
),
],
),
launch_action = xcode_schemes.launch_action(
_TOOLS["import_indexstores"],
diagnostics = _SCHEME_DIAGNOSTICS,
),
profile_action = xcode_schemes.profile_action(
_TOOLS["import_indexstores"],
build_configuration = "Release",
),
),
xcode_schemes.scheme(
name = "pbxproj_prefix",
launch_action = xcode_schemes.launch_action(
Expand Down Expand Up @@ -223,6 +242,7 @@ filegroup(
srcs = [
"//" + package_name() + "/extension_point_identifiers_parser:release_files",
"//" + package_name() + "/generators:release_files",
"//" + package_name() + "/import_indexstores:release_files",
"//" + package_name() + "/params_processors:release_files",
"//" + package_name() + "/swiftc_stub:release_files",
"//" + package_name() + "/xccurrentversions_parser:release_files",
Expand Down
59 changes: 59 additions & 0 deletions tools/import_indexstores/BUILD
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__"],
)
1 change: 1 addition & 0 deletions tools/import_indexstores/BUILD.release.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(["universal_import_indexstores"])
29 changes: 29 additions & 0 deletions tools/import_indexstores/Errors.swift
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
"""
}
}
216 changes: 216 additions & 0 deletions tools/import_indexstores/ImportIndexstores.swift
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
)
}
}
Loading
Loading