Skip to content

Commit

Permalink
WIP: swift calculate_output_groups
Browse files Browse the repository at this point in the history
Signed-off-by: Brentley Jones <github@brentleyjones.com>
Signed-off-by: Matt Pennig <mpennig@slack-corp.com>
  • Loading branch information
brentleyjones authored and pennig committed Jun 6, 2024
1 parent 3f53b4d commit 18b171c
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 0 deletions.
34 changes: 34 additions & 0 deletions tools/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ load("//xcodeproj:defs.bzl", "xcodeproj", "xcschemes")
load("//xcodeproj/internal:collections.bzl", "flatten", "uniq")

_TOOLS = {
"calculate_output_groups": "//tools/calculate_output_groups",
"files_and_groups": "//tools/generators/files_and_groups",
"import_indexstores": "//tools/import_indexstores",
"pbxnativetargets": "//tools/generators/pbxnativetargets",
Expand Down Expand Up @@ -51,6 +52,38 @@ _XCSCHEME_DIAGNOSTICS = xcschemes.diagnostics(
)

_XCSCHEMES = [
xcschemes.scheme(
name = "calculate_output_groups",
profile = xcschemes.profile(
launch_target = xcschemes.launch_target(
_TOOLS["calculate_output_groups"],
),
xcode_configuration = "Release",
),
run = xcschemes.run(
args = [
# colorDiagnostics
"NO",
# xcodeVersionActual
"1520",
# nonPreviewObjRoot
"/Users/brentley/Library/Developer/Xcode/DerivedData/tools-exxvdkcaoxdlhndlfnwxqvucohsr/Build/Intermediates.noindex",
# baseObjRoot
"/Users/brentley/Library/Developer/Xcode/DerivedData/tools-exxvdkcaoxdlhndlfnwxqvucohsr/Build/Intermediates.noindex",
# buildMarkerFile
"/Users/brentley/Library/Developer/Xcode/DerivedData/tools-exxvdkcaoxdlhndlfnwxqvucohsr/Build/Intermediates.noindex/build_marker",
# outputGroupPrefixes
"bc,bp,bi",
],
build_targets = [
_TOOLS["calculate_output_groups"],
],
diagnostics = _XCSCHEME_DIAGNOSTICS,
launch_target = xcschemes.launch_target(
_TOOLS["calculate_output_groups"],
),
),
),
xcschemes.scheme(
name = "files_and_groups",
profile = xcschemes.profile(
Expand Down Expand Up @@ -561,6 +594,7 @@ xcodeproj(
filegroup(
name = "release_files",
srcs = [
"//" + package_name() + "/calculate_output_groups:release_files",
"//" + package_name() + "/extension_point_identifiers_parser:release_files",
"//" + package_name() + "/generators:release_files",
"//" + package_name() + "/import_indexstores:release_files",
Expand Down
41 changes: 41 additions & 0 deletions tools/calculate_output_groups/Arguments.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import ArgumentParser
import Foundation

extension OutputGroupsCalculator {
struct Arguments: ParsableArguments {
@Argument(
help: "Value of the 'XCODE_VERSION_ACTUAL' environment variable."
)
var xcodeVersionActual: Int

@Argument(
help: """
Value of the 'OBJROOT' build setting when 'ENABLE_PREVIEWS = NO'.
""",
transform: { URL(fileURLWithPath: $0, isDirectory: true) }
)
var nonPreviewObjRoot: URL

@Argument(
help: """
Value of 'nonPreviewObjRoot' when 'INDEX_ENABLE_BUILD_ARENA = NO'.
""",
transform: { URL(fileURLWithPath: $0, isDirectory: true) }
)
var baseObjRoot: URL

@Argument(
help: """
Path to a file that has a ctime at or after the start of the build.
""",
transform: { URL(fileURLWithPath: $0, isDirectory: false) }
)
var buildMarkerFile: URL

@Argument(
help: "Comma seperated list of output group prefixes.",
transform: { $0.split(separator: ",").map(String.init) }
)
var outputGroupPrefixes: [String]
}
}
64 changes: 64 additions & 0 deletions tools/calculate_output_groups/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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 = "calculate_output_groups",
minimum_os_version = "12.0",
visibility = ["//visibility:public"],
deps = [":calculate_output_groups.library"],
)

swift_library(
name = "calculate_output_groups.library",
srcs = glob(["*.swift"]),
module_name = "calculate_output_groups",
deps = [
"//tools/lib/ToolCommon",
"@com_github_apple_swift_argument_parser//:ArgumentParser",
"@com_github_michaeleisel_zippyjson//:ZippyJSON",
],
)

swift_binary(
name = "calculate_output_groups_binary",
deps = [":calculate_output_groups.library"],
)

apple_universal_binary(
name = "universal_calculate_output_groups",
binary = ":calculate_output_groups_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_calculate_output_groups",
],
tags = ["manual"],
visibility = ["//:__subpackages__"],
)
1 change: 1 addition & 0 deletions tools/calculate_output_groups/BUILD.release.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(["universal_calculate_output_groups"])
31 changes: 31 additions & 0 deletions tools/calculate_output_groups/CalculateOutputGroups.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import ArgumentParser
import Darwin
import ToolCommon

@main
struct CalculateOutputGroups: AsyncParsableCommand {
@Argument(
help: "Value of the 'COLOR_DIAGNOSTICS' environment variable.",
transform: { $0 == "YES" }
)
var colorDiagnostics: Bool

@OptionGroup var arguments: OutputGroupsCalculator.Arguments

func run() async throws {
let logger = DefaultLogger(
standardError: StderrOutputStream(),
standardOutput: StdoutOutputStream(),
colorize: colorDiagnostics
)

let calculator = OutputGroupsCalculator()

do {
try await calculator.calculateOutputGroups(arguments: arguments)
} catch {
logger.logError(error.localizedDescription)
Darwin.exit(1)
}
}
}
126 changes: 126 additions & 0 deletions tools/calculate_output_groups/OutputGroupsCalculator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import Foundation
import ToolCommon
import ZippyJSON

struct OutputGroupsCalculator {
func calculateOutputGroups(arguments: Arguments) async throws {
let pifCache = arguments.baseObjRoot
.appendingPathComponent("XCBuildData/PIFCache")
let projectCache = pifCache.appendingPathComponent("project")
let targetCache = pifCache.appendingPathComponent("target")

let fileManager = FileManager.default

guard fileManager.fileExists(atPath: projectCache.path) &&
fileManager.fileExists(atPath: targetCache.path)
else {
throw UsageError(message: """
error: PIFCache (\(pifCache)) doesn't exist. If you manually cleared Derived \
Data, you need to close and re-open the project for the PIFCache to be created \
again. Using the "Clean Build Folder" command instead (⇧ ⌘ K) won't trigger \
this error. If this error still happens after re-opening the project, please \
file a bug report here: \
https://github.com/MobileNativeFoundation/rules_xcodeproj/issues/new?template=bug.md
""")
}

let projectURL = try Self.findProjectURL(in: projectCache)
let project = try Self.decodeProject(at: projectURL)
let targets =
try await Self.decodeTargets(project.targets, in: targetCache)

dump(targets)
}

static func findProjectURL(in projectCache: URL) throws -> URL {
let projectPIFsEnumerator = FileManager.default.enumerator(
at: projectCache,
includingPropertiesForKeys: [.contentModificationDateKey],
options: [
.skipsHiddenFiles,
.skipsPackageDescendants,
.skipsSubdirectoryDescendants,
]
)!

var newestProjectPIF: URL?
var newestProjectPIFDate = Date.distantPast
for case let projectPIF as URL in projectPIFsEnumerator {
guard let resourceValues = try? projectPIF.resourceValues(
forKeys: [.contentModificationDateKey]
), let modificationDate = resourceValues.contentModificationDate
else {
continue
}

// TODO: The modification date is in the filename, should we use
// that instead?
if modificationDate > newestProjectPIFDate {
newestProjectPIF = projectPIF
newestProjectPIFDate = modificationDate
}
}

guard let projectPIF = newestProjectPIF else {
throw UsageError(message: """
error: Couldn't find a Project PIF at "\(projectCache)". Please file a bug \
report here: https://github.com/MobileNativeFoundation/rules_xcodeproj/issues/new?template=bug.md
""")
}

return projectPIF
}

static func decodeProject(at url: URL) throws -> ProjectPIF {
let decoder = ZippyJSONDecoder()
return try decoder.decode(ProjectPIF.self, from: Data(contentsOf: url))
}

static func decodeTargets(
_ targets: [String],
in targetCache: URL
) async throws -> [TargetPIF] {
return try await withThrowingTaskGroup(
of: TargetPIF.self,
returning: [TargetPIF].self
) { group in
for target in targets {
group.addTask {
let url =
targetCache.appendingPathComponent("\(target)-json")
let decoder = ZippyJSONDecoder()
return try decoder
.decode(TargetPIF.self, from: Data(contentsOf: url))
}
}

var targetPIFs: [TargetPIF] = []
for try await target in group {
targetPIFs.append(target)
}

return targetPIFs
}
}
}

struct ProjectPIF: Decodable {
let targets: [String]
}

struct TargetPIF: Decodable {
struct BuildConfiguration: Decodable {
let name: String
let buildSettings: [String: String]
}

let guid: String
let buildConfigurations: [BuildConfiguration]
}

struct Target {
let label: String

// Maps Platform Name -> [Target ID]
let targetIds: [String: [String]]
}

0 comments on commit 18b171c

Please sign in to comment.