Skip to content

Commit 18b171c

Browse files
brentleyjonespennig
authored andcommitted
WIP: swift calculate_output_groups
Signed-off-by: Brentley Jones <github@brentleyjones.com> Signed-off-by: Matt Pennig <mpennig@slack-corp.com>
1 parent 3f53b4d commit 18b171c

File tree

6 files changed

+297
-0
lines changed

6 files changed

+297
-0
lines changed

tools/BUILD

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ load("//xcodeproj:defs.bzl", "xcodeproj", "xcschemes")
44
load("//xcodeproj/internal:collections.bzl", "flatten", "uniq")
55

66
_TOOLS = {
7+
"calculate_output_groups": "//tools/calculate_output_groups",
78
"files_and_groups": "//tools/generators/files_and_groups",
89
"import_indexstores": "//tools/import_indexstores",
910
"pbxnativetargets": "//tools/generators/pbxnativetargets",
@@ -51,6 +52,38 @@ _XCSCHEME_DIAGNOSTICS = xcschemes.diagnostics(
5152
)
5253

5354
_XCSCHEMES = [
55+
xcschemes.scheme(
56+
name = "calculate_output_groups",
57+
profile = xcschemes.profile(
58+
launch_target = xcschemes.launch_target(
59+
_TOOLS["calculate_output_groups"],
60+
),
61+
xcode_configuration = "Release",
62+
),
63+
run = xcschemes.run(
64+
args = [
65+
# colorDiagnostics
66+
"NO",
67+
# xcodeVersionActual
68+
"1520",
69+
# nonPreviewObjRoot
70+
"/Users/brentley/Library/Developer/Xcode/DerivedData/tools-exxvdkcaoxdlhndlfnwxqvucohsr/Build/Intermediates.noindex",
71+
# baseObjRoot
72+
"/Users/brentley/Library/Developer/Xcode/DerivedData/tools-exxvdkcaoxdlhndlfnwxqvucohsr/Build/Intermediates.noindex",
73+
# buildMarkerFile
74+
"/Users/brentley/Library/Developer/Xcode/DerivedData/tools-exxvdkcaoxdlhndlfnwxqvucohsr/Build/Intermediates.noindex/build_marker",
75+
# outputGroupPrefixes
76+
"bc,bp,bi",
77+
],
78+
build_targets = [
79+
_TOOLS["calculate_output_groups"],
80+
],
81+
diagnostics = _XCSCHEME_DIAGNOSTICS,
82+
launch_target = xcschemes.launch_target(
83+
_TOOLS["calculate_output_groups"],
84+
),
85+
),
86+
),
5487
xcschemes.scheme(
5588
name = "files_and_groups",
5689
profile = xcschemes.profile(
@@ -561,6 +594,7 @@ xcodeproj(
561594
filegroup(
562595
name = "release_files",
563596
srcs = [
597+
"//" + package_name() + "/calculate_output_groups:release_files",
564598
"//" + package_name() + "/extension_point_identifiers_parser:release_files",
565599
"//" + package_name() + "/generators:release_files",
566600
"//" + package_name() + "/import_indexstores:release_files",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import ArgumentParser
2+
import Foundation
3+
4+
extension OutputGroupsCalculator {
5+
struct Arguments: ParsableArguments {
6+
@Argument(
7+
help: "Value of the 'XCODE_VERSION_ACTUAL' environment variable."
8+
)
9+
var xcodeVersionActual: Int
10+
11+
@Argument(
12+
help: """
13+
Value of the 'OBJROOT' build setting when 'ENABLE_PREVIEWS = NO'.
14+
""",
15+
transform: { URL(fileURLWithPath: $0, isDirectory: true) }
16+
)
17+
var nonPreviewObjRoot: URL
18+
19+
@Argument(
20+
help: """
21+
Value of 'nonPreviewObjRoot' when 'INDEX_ENABLE_BUILD_ARENA = NO'.
22+
""",
23+
transform: { URL(fileURLWithPath: $0, isDirectory: true) }
24+
)
25+
var baseObjRoot: URL
26+
27+
@Argument(
28+
help: """
29+
Path to a file that has a ctime at or after the start of the build.
30+
""",
31+
transform: { URL(fileURLWithPath: $0, isDirectory: false) }
32+
)
33+
var buildMarkerFile: URL
34+
35+
@Argument(
36+
help: "Comma seperated list of output group prefixes.",
37+
transform: { $0.split(separator: ",").map(String.init) }
38+
)
39+
var outputGroupPrefixes: [String]
40+
}
41+
}

tools/calculate_output_groups/BUILD

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
load("@build_bazel_rules_apple//apple:apple.bzl", "apple_universal_binary")
2+
load(
3+
"@build_bazel_rules_apple//apple:macos.bzl",
4+
"macos_command_line_application",
5+
)
6+
load(
7+
"@build_bazel_rules_swift//swift:swift.bzl",
8+
"swift_binary",
9+
"swift_library",
10+
)
11+
12+
# This target exists to keep configurations the same between the generator
13+
# and the tests, which makes the Xcode development experience better. If we used
14+
# `swift_binary` or `apple_universal_binary` in `xcodeproj`, then the
15+
# `macos_unit_test` transition (which is used to be able to set a minimum os
16+
# version on the tests) will create slightly different configurations for our
17+
# `swift_library`s. Maybe https://github.com/bazelbuild/bazel/issues/6526 will
18+
# fix that for us.
19+
macos_command_line_application(
20+
name = "calculate_output_groups",
21+
minimum_os_version = "12.0",
22+
visibility = ["//visibility:public"],
23+
deps = [":calculate_output_groups.library"],
24+
)
25+
26+
swift_library(
27+
name = "calculate_output_groups.library",
28+
srcs = glob(["*.swift"]),
29+
module_name = "calculate_output_groups",
30+
deps = [
31+
"//tools/lib/ToolCommon",
32+
"@com_github_apple_swift_argument_parser//:ArgumentParser",
33+
"@com_github_michaeleisel_zippyjson//:ZippyJSON",
34+
],
35+
)
36+
37+
swift_binary(
38+
name = "calculate_output_groups_binary",
39+
deps = [":calculate_output_groups.library"],
40+
)
41+
42+
apple_universal_binary(
43+
name = "universal_calculate_output_groups",
44+
binary = ":calculate_output_groups_binary",
45+
forced_cpus = [
46+
"x86_64",
47+
"arm64",
48+
],
49+
minimum_os_version = "12.0",
50+
platform_type = "macos",
51+
visibility = ["//visibility:public"],
52+
)
53+
54+
# Release
55+
56+
filegroup(
57+
name = "release_files",
58+
srcs = [
59+
"BUILD.release.bazel",
60+
":universal_calculate_output_groups",
61+
],
62+
tags = ["manual"],
63+
visibility = ["//:__subpackages__"],
64+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports_files(["universal_calculate_output_groups"])
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import ArgumentParser
2+
import Darwin
3+
import ToolCommon
4+
5+
@main
6+
struct CalculateOutputGroups: AsyncParsableCommand {
7+
@Argument(
8+
help: "Value of the 'COLOR_DIAGNOSTICS' environment variable.",
9+
transform: { $0 == "YES" }
10+
)
11+
var colorDiagnostics: Bool
12+
13+
@OptionGroup var arguments: OutputGroupsCalculator.Arguments
14+
15+
func run() async throws {
16+
let logger = DefaultLogger(
17+
standardError: StderrOutputStream(),
18+
standardOutput: StdoutOutputStream(),
19+
colorize: colorDiagnostics
20+
)
21+
22+
let calculator = OutputGroupsCalculator()
23+
24+
do {
25+
try await calculator.calculateOutputGroups(arguments: arguments)
26+
} catch {
27+
logger.logError(error.localizedDescription)
28+
Darwin.exit(1)
29+
}
30+
}
31+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import Foundation
2+
import ToolCommon
3+
import ZippyJSON
4+
5+
struct OutputGroupsCalculator {
6+
func calculateOutputGroups(arguments: Arguments) async throws {
7+
let pifCache = arguments.baseObjRoot
8+
.appendingPathComponent("XCBuildData/PIFCache")
9+
let projectCache = pifCache.appendingPathComponent("project")
10+
let targetCache = pifCache.appendingPathComponent("target")
11+
12+
let fileManager = FileManager.default
13+
14+
guard fileManager.fileExists(atPath: projectCache.path) &&
15+
fileManager.fileExists(atPath: targetCache.path)
16+
else {
17+
throw UsageError(message: """
18+
error: PIFCache (\(pifCache)) doesn't exist. If you manually cleared Derived \
19+
Data, you need to close and re-open the project for the PIFCache to be created \
20+
again. Using the "Clean Build Folder" command instead (⇧ ⌘ K) won't trigger \
21+
this error. If this error still happens after re-opening the project, please \
22+
file a bug report here: \
23+
https://github.com/MobileNativeFoundation/rules_xcodeproj/issues/new?template=bug.md
24+
""")
25+
}
26+
27+
let projectURL = try Self.findProjectURL(in: projectCache)
28+
let project = try Self.decodeProject(at: projectURL)
29+
let targets =
30+
try await Self.decodeTargets(project.targets, in: targetCache)
31+
32+
dump(targets)
33+
}
34+
35+
static func findProjectURL(in projectCache: URL) throws -> URL {
36+
let projectPIFsEnumerator = FileManager.default.enumerator(
37+
at: projectCache,
38+
includingPropertiesForKeys: [.contentModificationDateKey],
39+
options: [
40+
.skipsHiddenFiles,
41+
.skipsPackageDescendants,
42+
.skipsSubdirectoryDescendants,
43+
]
44+
)!
45+
46+
var newestProjectPIF: URL?
47+
var newestProjectPIFDate = Date.distantPast
48+
for case let projectPIF as URL in projectPIFsEnumerator {
49+
guard let resourceValues = try? projectPIF.resourceValues(
50+
forKeys: [.contentModificationDateKey]
51+
), let modificationDate = resourceValues.contentModificationDate
52+
else {
53+
continue
54+
}
55+
56+
// TODO: The modification date is in the filename, should we use
57+
// that instead?
58+
if modificationDate > newestProjectPIFDate {
59+
newestProjectPIF = projectPIF
60+
newestProjectPIFDate = modificationDate
61+
}
62+
}
63+
64+
guard let projectPIF = newestProjectPIF else {
65+
throw UsageError(message: """
66+
error: Couldn't find a Project PIF at "\(projectCache)". Please file a bug \
67+
report here: https://github.com/MobileNativeFoundation/rules_xcodeproj/issues/new?template=bug.md
68+
""")
69+
}
70+
71+
return projectPIF
72+
}
73+
74+
static func decodeProject(at url: URL) throws -> ProjectPIF {
75+
let decoder = ZippyJSONDecoder()
76+
return try decoder.decode(ProjectPIF.self, from: Data(contentsOf: url))
77+
}
78+
79+
static func decodeTargets(
80+
_ targets: [String],
81+
in targetCache: URL
82+
) async throws -> [TargetPIF] {
83+
return try await withThrowingTaskGroup(
84+
of: TargetPIF.self,
85+
returning: [TargetPIF].self
86+
) { group in
87+
for target in targets {
88+
group.addTask {
89+
let url =
90+
targetCache.appendingPathComponent("\(target)-json")
91+
let decoder = ZippyJSONDecoder()
92+
return try decoder
93+
.decode(TargetPIF.self, from: Data(contentsOf: url))
94+
}
95+
}
96+
97+
var targetPIFs: [TargetPIF] = []
98+
for try await target in group {
99+
targetPIFs.append(target)
100+
}
101+
102+
return targetPIFs
103+
}
104+
}
105+
}
106+
107+
struct ProjectPIF: Decodable {
108+
let targets: [String]
109+
}
110+
111+
struct TargetPIF: Decodable {
112+
struct BuildConfiguration: Decodable {
113+
let name: String
114+
let buildSettings: [String: String]
115+
}
116+
117+
let guid: String
118+
let buildConfigurations: [BuildConfiguration]
119+
}
120+
121+
struct Target {
122+
let label: String
123+
124+
// Maps Platform Name -> [Target ID]
125+
let targetIds: [String: [String]]
126+
}

0 commit comments

Comments
 (0)