From 4649c2512db2d54ea6535fdebdfb3057d2f41d88 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Thu, 12 Sep 2024 15:43:57 +0200 Subject: [PATCH 01/20] first commit --- swift/runfiles/BUILD | 10 ++ swift/runfiles/Runfiles.swift | 285 ++++++++++++++++++++++++++++++++++ swift/swift_library.bzl | 13 ++ 3 files changed, 308 insertions(+) create mode 100644 swift/runfiles/BUILD create mode 100644 swift/runfiles/Runfiles.swift diff --git a/swift/runfiles/BUILD b/swift/runfiles/BUILD new file mode 100644 index 000000000..c211d4cca --- /dev/null +++ b/swift/runfiles/BUILD @@ -0,0 +1,10 @@ +load("//swift:swift_library.bzl", "swift_library") + +swift_library( + name = "runfiles", + srcs = [ + "Runfiles.swift", + ], + module_name = "BazelRunfiles", + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/swift/runfiles/Runfiles.swift b/swift/runfiles/Runfiles.swift new file mode 100644 index 000000000..0bc93ab16 --- /dev/null +++ b/swift/runfiles/Runfiles.swift @@ -0,0 +1,285 @@ +import Foundation + +protocol LookupStrategy { + func rlocationChecked(path: String) -> URL? + func envVars() -> [String: String] +} + +struct DirectoryBased: LookupStrategy { + + private let runfilesRoot: URL + init(path: URL) { + runfilesRoot = path + } + + func rlocationChecked(path: String) -> URL? { + runfilesRoot.appending(path: path) + } + + func envVars() -> [String: String] { + [ + "RUNFILES_DIR": runfilesRoot.path, + ] + } +} + +struct ManifestBased: LookupStrategy { + + private let manifestPath: URL + private let runfiles: [String: String] + + init(manifestPath: URL) { + self.manifestPath = manifestPath + runfiles = Self.loadRunfiles(from: manifestPath) + } + + func rlocationChecked(path: String) -> URL? { + if let runfile = runfiles[path] { + return URL(filePath: runfile) + } + + // Search for prefixes in the path + var prefixEnd = path.lastIndex(of: "/") + + while true { + guard let end = prefixEnd else { + return nil + } + + let prefix = String(path[.. [String: String] { + guard let runfilesDir = Self.getRunfilesDir(fromManifestPath: manifestPath) else { + return [:] + } + return [ + "RUNFILES_MANIFEST_FILE": manifestPath.path, + "RUNFILES_DIR": runfilesDir.path, + ] + } + + static func getRunfilesDir(fromManifestPath path: URL) -> URL? { + let lastComponent = path.lastPathComponent + + if lastComponent == "MANIFEST" { + return path.deletingLastPathComponent() + } + if lastComponent == ".runfiles_manifest" { + let newPath = path.deletingLastPathComponent().appendingPathComponent( + path.lastPathComponent.replacingOccurrences(of: "_manifest", with: "") + ) + return newPath + } + return nil + } + + static func loadRunfiles(from manifestPath: URL) -> [String: String] { + guard let fileHandle = try? FileHandle(forReadingFrom: manifestPath) else { + // If the file doesn't exist, return an empty dictionary. + return [:] + } + defer { + try? fileHandle.close() + } + + var pathMapping = [String: String]() + if let data = try? fileHandle.readToEnd(), let content = String(data: data, encoding: .utf8) { + let lines = content.split(separator: "\n") + for line in lines { + let fields = line.components(separatedBy: " ") + if fields.count == 1 { + pathMapping[fields[0]] = fields[0] + } else { + pathMapping[fields[0]] = fields[1] + } + } + } + + return pathMapping + } +} + +struct RepoMappingKey: Hashable { + let sourceRepoCanonicalName: String + let targetRepoApparentName: String +} + +public enum RunfilesError: Error { + case error +} + +public final class Runfiles { + + // Value is the runfiles directory of target repository + private let repoMapping: [RepoMappingKey: String] + private let strategy: LookupStrategy + + init(strategy: LookupStrategy, repoMapping: [RepoMappingKey: String]) { + self.strategy = strategy + self.repoMapping = repoMapping + } + + public func rlocation(_ path: String, sourceRepository: String) -> URL? { + guard !path.hasPrefix("../"), + !path.contains("/.."), + !path.hasPrefix("./"), + !path.contains("/./"), + !path.hasSuffix("/."), + !path.contains("//") else { + return nil + } + guard path.first != "\\" else { + return nil + } + guard path.first != "/" else { + return URL(filePath: path) + } + + // Split off the first path component, which contains the repository + // name (apparent or canonical). + let components = path.split(separator: ",", maxSplits: 1) + let targetRepository = String(components[0]) + let key = RepoMappingKey(sourceRepoCanonicalName: sourceRepository, targetRepoApparentName: targetRepository) + + if components.count == 1 || repoMapping[key] == nil { + // One of the following is the case: + // - not using Bzlmod, so the repository mapping is empty and + // apparent and canonical repository names are the same + // - target_repo is already a canonical repository name and does not + // have to be mapped. + // - path did not contain a slash and referred to a root symlink, + // which also should not be mapped. + return strategy.rlocationChecked(path: path) + } + + let remainingPath = String(components[1]) + + // target_repo is an apparent repository name. Look up the corresponding + // canonical repository name with respect to the current repository, + // identified by its canonical name. + let targetCanonical = repoMapping[key] + return strategy.rlocationChecked(path: targetRepository + "/" + remainingPath) + } + + public func envVars() -> [String: String] { + strategy.envVars() + } + + // MARK: Factory method + + public static func create(environment: [String: String]? = nil) throws -> Runfiles? { + + let environment = environment ?? ProcessInfo.processInfo.environment + + let strategy: LookupStrategy + if let manifestFile = environment["RUNFILES_MANIFEST_FILE"] { + strategy = ManifestBased(manifestPath: URL(filePath: manifestFile)) + } else { + strategy = try DirectoryBased(path: findRunfilesDir()) + } + + // If the repository mapping file can't be found, that is not an error: We + // might be running without Bzlmod enabled or there may not be any runfiles. + // In this case, just apply an empty repo mapping. + let repoMapping = try strategy.rlocationChecked(path: "_repo_mapping").map { repoMappingFile in + try parseRepoMapping(path: repoMappingFile) + } ?? [:] + + return Runfiles(strategy: strategy, repoMapping: repoMapping) + } + +} + +// MARK: Parsing Repo Mapping + +func parseRepoMapping(path: URL) throws -> [RepoMappingKey: String] { + guard let fileHandle = try? FileHandle(forReadingFrom: path) else { + // If the repository mapping file can't be found, that is not an error: We + // might be running without Bzlmod enabled or there may not be any runfiles. + // In this case, just apply an empty repo mapping. + return [:] + } + defer { + try? fileHandle.close() + } + + var repoMapping = [RepoMappingKey: String]() + if let data = try? fileHandle.readToEnd(), let content = String(data: data, encoding: .utf8) { + let lines = content.split(separator: "\n") + for line in lines { + let fields = line.components(separatedBy: ",") + if fields.count != 3 { + throw RunfilesError.error + } + let key = RepoMappingKey( + sourceRepoCanonicalName: fields[0], + targetRepoApparentName: fields[1] + ) + repoMapping[key] = fields[2] + } + } + + return repoMapping +} + +// MARK: Finding Runfiles Directory + +func findRunfilesDir() throws -> URL { + if let runfilesDirPath = ProcessInfo.processInfo.environment["RUNFILES_DIR"], + let runfilesDirURL = URL(string: runfilesDirPath), + FileManager.default.fileExists(atPath: runfilesDirURL.path, isDirectory: nil) { + return runfilesDirURL + } + + if let testSrcdirPath = ProcessInfo.processInfo.environment["TEST_SRCDIR"], + let testSrcdirURL = URL(string: testSrcdirPath), + FileManager.default.fileExists(atPath: testSrcdirURL.path, isDirectory: nil) { + return testSrcdirURL + } + + // Consume the first argument (argv[0]) + guard let execPath = CommandLine.arguments.first else { + throw RunfilesError.error + } + + var binaryPath = URL(fileURLWithPath: execPath) + + while true { + // Check for our neighboring $binary.runfiles directory. + let runfilesName = binaryPath.lastPathComponent + ".runfiles" + let runfilesPath = binaryPath.deletingLastPathComponent().appendingPathComponent(runfilesName) + + if FileManager.default.fileExists(atPath: runfilesPath.path, isDirectory: nil) { + return runfilesPath + } + + // Check if we're already under a *.runfiles directory. + var ancestorURL = binaryPath.deletingLastPathComponent() + while ancestorURL.path != "/" { + if ancestorURL.lastPathComponent.hasSuffix(".runfiles") { + return ancestorURL + } + ancestorURL.deleteLastPathComponent() + } + + // Check if it's a symlink and follow it. + if let symlinkTarget = try? FileManager.default.destinationOfSymbolicLink(atPath: binaryPath.path) { + let linkTargetURL = URL( + fileURLWithPath: symlinkTarget, + relativeTo: binaryPath.deletingLastPathComponent() + ) + binaryPath = linkTargetURL + } else { + break + } + } + + throw RunfilesError.error +} diff --git a/swift/swift_library.bzl b/swift/swift_library.bzl index 3d5713768..72004270f 100644 --- a/swift/swift_library.bzl +++ b/swift/swift_library.bzl @@ -111,6 +111,19 @@ def _swift_library_impl(ctx): linkopts = expand_make_variables(ctx, linkopts, "linkopts") srcs = ctx.files.srcs + matches = [dep for dep in ctx.attr.deps if dep.label == Label("@@rules_swift~//swift/runfiles:runfiles")] + if len(matches) > 0: + repo_name_file = ctx.actions.declare_file("RunfilesRepoName.swift") + ctx.actions.write( + output=repo_name_file, + content=""" + enum BazelRunfilesConstants {{ + static let repoName = "{}" + }} + """.format(ctx.label.name), + ) + srcs = srcs + [repo_name_file] + module_copts = additional_per_module_swiftcopts( ctx.label, ctx.attr._per_module_swiftcopt[PerModuleSwiftCoptSettingInfo], From d8df061544e99ff33d7cc870e16e84f4df5685d3 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Fri, 13 Sep 2024 16:10:22 +0200 Subject: [PATCH 02/20] workspace_name and not name --- swift/swift_library.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swift/swift_library.bzl b/swift/swift_library.bzl index 72004270f..1ccf9458d 100644 --- a/swift/swift_library.bzl +++ b/swift/swift_library.bzl @@ -120,7 +120,7 @@ def _swift_library_impl(ctx): enum BazelRunfilesConstants {{ static let repoName = "{}" }} - """.format(ctx.label.name), + """.format(ctx.label.workspace_name), ) srcs = srcs + [repo_name_file] From 9aca5fd584428ae988e0dd8ceeccdaafb47ca4ce Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Fri, 13 Sep 2024 16:11:51 +0200 Subject: [PATCH 03/20] specify internal accessibility for constant enum --- swift/swift_library.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swift/swift_library.bzl b/swift/swift_library.bzl index 1ccf9458d..367ff0a4c 100644 --- a/swift/swift_library.bzl +++ b/swift/swift_library.bzl @@ -117,7 +117,7 @@ def _swift_library_impl(ctx): ctx.actions.write( output=repo_name_file, content=""" - enum BazelRunfilesConstants {{ + internal enum BazelRunfilesConstants {{ static let repoName = "{}" }} """.format(ctx.label.workspace_name), From 19ad987e2bcecf64d2aa663808f32f9ac8400e6b Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 17 Sep 2024 20:13:29 +0200 Subject: [PATCH 04/20] api fixes --- swift/runfiles/Runfiles.swift | 89 ++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 34 deletions(-) diff --git a/swift/runfiles/Runfiles.swift b/swift/runfiles/Runfiles.swift index 0bc93ab16..10a25b7d8 100644 --- a/swift/runfiles/Runfiles.swift +++ b/swift/runfiles/Runfiles.swift @@ -1,3 +1,17 @@ +// Copyright 2024 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import Foundation protocol LookupStrategy { @@ -28,9 +42,9 @@ struct ManifestBased: LookupStrategy { private let manifestPath: URL private let runfiles: [String: String] - init(manifestPath: URL) { + init(manifestPath: URL) throws { self.manifestPath = manifestPath - runfiles = Self.loadRunfiles(from: manifestPath) + runfiles = try Self.loadRunfiles(from: manifestPath) } func rlocationChecked(path: String) -> URL? { @@ -56,12 +70,10 @@ struct ManifestBased: LookupStrategy { } func envVars() -> [String: String] { - guard let runfilesDir = Self.getRunfilesDir(fromManifestPath: manifestPath) else { - return [:] - } + let runfilesDir = Self.getRunfilesDir(fromManifestPath: manifestPath) return [ "RUNFILES_MANIFEST_FILE": manifestPath.path, - "RUNFILES_DIR": runfilesDir.path, + "RUNFILES_DIR": runfilesDir?.path ?? "", ] } @@ -71,7 +83,7 @@ struct ManifestBased: LookupStrategy { if lastComponent == "MANIFEST" { return path.deletingLastPathComponent() } - if lastComponent == ".runfiles_manifest" { + if lastComponent.hasSuffix(".runfiles_manifest") { let newPath = path.deletingLastPathComponent().appendingPathComponent( path.lastPathComponent.replacingOccurrences(of: "_manifest", with: "") ) @@ -80,10 +92,9 @@ struct ManifestBased: LookupStrategy { return nil } - static func loadRunfiles(from manifestPath: URL) -> [String: String] { + static func loadRunfiles(from manifestPath: URL) throws -> [String: String] { guard let fileHandle = try? FileHandle(forReadingFrom: manifestPath) else { - // If the file doesn't exist, return an empty dictionary. - return [:] + throw RunfilesError.error } defer { try? fileHandle.close() @@ -93,11 +104,11 @@ struct ManifestBased: LookupStrategy { if let data = try? fileHandle.readToEnd(), let content = String(data: data, encoding: .utf8) { let lines = content.split(separator: "\n") for line in lines { - let fields = line.components(separatedBy: " ") + let fields = line.split(separator: " ", maxSplits: 1) if fields.count == 1 { - pathMapping[fields[0]] = fields[0] + pathMapping[String(fields[0])] = String(fields[0]) } else { - pathMapping[fields[0]] = fields[1] + pathMapping[String(fields[0])] = String(fields[1]) } } } @@ -117,16 +128,18 @@ public enum RunfilesError: Error { public final class Runfiles { + private let strategy: LookupStrategy // Value is the runfiles directory of target repository private let repoMapping: [RepoMappingKey: String] - private let strategy: LookupStrategy + private let sourceRepository: String - init(strategy: LookupStrategy, repoMapping: [RepoMappingKey: String]) { + init(strategy: LookupStrategy, repoMapping: [RepoMappingKey: String], sourceRepository: String) { self.strategy = strategy self.repoMapping = repoMapping + self.sourceRepository = sourceRepository } - public func rlocation(_ path: String, sourceRepository: String) -> URL? { + public func rlocation(_ path: String, sourceRepository: String? = nil) -> URL? { guard !path.hasPrefix("../"), !path.contains("/.."), !path.hasPrefix("./"), @@ -142,11 +155,14 @@ public final class Runfiles { return URL(filePath: path) } + let sourceRepository = sourceRepository ?? self.sourceRepository + // Split off the first path component, which contains the repository // name (apparent or canonical). - let components = path.split(separator: ",", maxSplits: 1) + let components = path.split(separator: "/", maxSplits: 1) let targetRepository = String(components[0]) let key = RepoMappingKey(sourceRepoCanonicalName: sourceRepository, targetRepoApparentName: targetRepository) + print("corentin", key) if components.count == 1 || repoMapping[key] == nil { // One of the following is the case: @@ -164,8 +180,11 @@ public final class Runfiles { // target_repo is an apparent repository name. Look up the corresponding // canonical repository name with respect to the current repository, // identified by its canonical name. - let targetCanonical = repoMapping[key] - return strategy.rlocationChecked(path: targetRepository + "/" + remainingPath) + if let targetCanonical = repoMapping[key] { + return strategy.rlocationChecked(path: targetCanonical + "/" + remainingPath) + } else { + return strategy.rlocationChecked(path: path) + } } public func envVars() -> [String: String] { @@ -174,15 +193,15 @@ public final class Runfiles { // MARK: Factory method - public static func create(environment: [String: String]? = nil) throws -> Runfiles? { + public static func create(sourceRepository: String, environment: [String: String]? = nil) throws -> Runfiles { let environment = environment ?? ProcessInfo.processInfo.environment let strategy: LookupStrategy if let manifestFile = environment["RUNFILES_MANIFEST_FILE"] { - strategy = ManifestBased(manifestPath: URL(filePath: manifestFile)) + strategy = try ManifestBased(manifestPath: URL(filePath: manifestFile)) } else { - strategy = try DirectoryBased(path: findRunfilesDir()) + strategy = try DirectoryBased(path: findRunfilesDir(environment: environment)) } // If the repository mapping file can't be found, that is not an error: We @@ -192,7 +211,7 @@ public final class Runfiles { try parseRepoMapping(path: repoMappingFile) } ?? [:] - return Runfiles(strategy: strategy, repoMapping: repoMapping) + return Runfiles(strategy: strategy, repoMapping: repoMapping, sourceRepository: sourceRepository) } } @@ -222,7 +241,7 @@ func parseRepoMapping(path: URL) throws -> [RepoMappingKey: String] { sourceRepoCanonicalName: fields[0], targetRepoApparentName: fields[1] ) - repoMapping[key] = fields[2] + repoMapping[key] = fields[2] // mapping } } @@ -231,17 +250,19 @@ func parseRepoMapping(path: URL) throws -> [RepoMappingKey: String] { // MARK: Finding Runfiles Directory -func findRunfilesDir() throws -> URL { - if let runfilesDirPath = ProcessInfo.processInfo.environment["RUNFILES_DIR"], - let runfilesDirURL = URL(string: runfilesDirPath), - FileManager.default.fileExists(atPath: runfilesDirURL.path, isDirectory: nil) { - return runfilesDirURL +func findRunfilesDir(environment: [String: String]) throws -> URL { + if let runfilesDirPath = environment["RUNFILES_DIR"] { + let runfilesDirURL = URL(filePath: runfilesDirPath) + if FileManager.default.fileExists(atPath: runfilesDirURL.path, isDirectory: nil) { + return runfilesDirURL + } } - if let testSrcdirPath = ProcessInfo.processInfo.environment["TEST_SRCDIR"], - let testSrcdirURL = URL(string: testSrcdirPath), - FileManager.default.fileExists(atPath: testSrcdirURL.path, isDirectory: nil) { - return testSrcdirURL + if let testSrcdirPath = environment["TEST_SRCDIR"] { + let testSrcdirURL = URL(filePath: testSrcdirPath) + if FileManager.default.fileExists(atPath: testSrcdirURL.path, isDirectory: nil) { + return testSrcdirURL + } } // Consume the first argument (argv[0]) @@ -249,7 +270,7 @@ func findRunfilesDir() throws -> URL { throw RunfilesError.error } - var binaryPath = URL(fileURLWithPath: execPath) + var binaryPath = URL(filePath: execPath) while true { // Check for our neighboring $binary.runfiles directory. From 647bab2bfe5843aa3e867edbe0f840056bf8bc0c Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 17 Sep 2024 20:13:43 +0200 Subject: [PATCH 05/20] add tests --- test/runfiles/BUILD | 9 + test/runfiles/RunfilesTests.swift | 419 ++++++++++++++++++++++++++++++ 2 files changed, 428 insertions(+) create mode 100644 test/runfiles/BUILD create mode 100644 test/runfiles/RunfilesTests.swift diff --git a/test/runfiles/BUILD b/test/runfiles/BUILD new file mode 100644 index 000000000..be733625d --- /dev/null +++ b/test/runfiles/BUILD @@ -0,0 +1,9 @@ +load("//swift:swift_test.bzl", "swift_test") + +swift_test( + name = "RunfilesTests", + srcs = ["RunfilesTests.swift"], + deps = [ + "@build_bazel_rules_swift//swift/runfiles", + ], +) diff --git a/test/runfiles/RunfilesTests.swift b/test/runfiles/RunfilesTests.swift new file mode 100644 index 000000000..48844000e --- /dev/null +++ b/test/runfiles/RunfilesTests.swift @@ -0,0 +1,419 @@ +// Copyright 2024 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import XCTest +import BazelRunfiles +import Foundation + +// Mainly adapted from https://github.com/bazelbuild/rules_python/blob/main/tests/runfiles/runfiles_test.py +final class RunfilesTests: XCTestCase { + + func testRlocationArgumentValidation() throws { + + let (fileURL, clean) = try createMockFile(name: "MANIFEST", contents: "a/b /c/d"); defer { try? clean() } + + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_MANIFEST_FILE": fileURL.path, + "RUNFILES_DIR": "ignored when RUNFILES_MANIFEST_FILE has a value", + "TEST_SRCDIR": "always ignored", + ] + ) + XCTAssertEqual(runfiles.rlocation("a/b")?.path, "/c/d") + XCTAssertNil(runfiles.rlocation("foo")) + } + + func testManifestBasedRunfilesEnvVarsFromManifest() throws { + + let (manifest, clean) = try createMockFile(name: "MANIFEST", contents: "a/b /c/d"); defer { try? clean() } + + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", + ] + ) + + XCTAssertEqual(runfiles.envVars(), [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "RUNFILES_DIR": manifest.deletingLastPathComponent().path, + ]) + } + + func testManifestBasedRunfilesEnvVarsFromRunfilesManifest() throws { + let (manifest, clean) = try createMockFile(name: "foo.runfiles_manifest", contents: "a/b /c/d"); defer { try? clean() } + + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", + ] + ) + + XCTAssertEqual(runfiles.envVars(), [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "RUNFILES_DIR": manifest.deletingLastPathComponent().appendingPathComponent("foo.runfiles").path, + ]) + } + + func testManifestBasedRunfilesEnvVarsFromArbitraryManifest() throws { + + let (manifest, clean) = try createMockFile(name: "x_manifest", contents: "a/b /c/d"); defer { try? clean() } + + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", + ] + ) + + XCTAssertEqual(runfiles.envVars(), [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "RUNFILES_DIR": "", + ]) + + } + + func testCreatesDirectoryBasedRunfiles() throws { + + let (runfilesDir, clean) = try createMockDirectory(name: "my_custom_runfiles"); defer { try? clean() } + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_DIR": runfilesDir.path, + "TEST_SRCDIR": "always ignored", + ] + ) + + XCTAssertEqual(runfiles.rlocation("a/b")?.path, runfilesDir.path + "/" + "a/b") + XCTAssertEqual(runfiles.rlocation("foo")?.path, runfilesDir.path + "/" + "foo") + } + + func testCreatesDirectoryBasedRunfilesEnvVars() throws { + + let (runfilesDir, clean) = try createMockDirectory(name: "my_custom_runfiles"); defer { try? clean() } + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_DIR": runfilesDir.path, + "TEST_SRCDIR": "always ignored", + ] + ) + + XCTAssertEqual(runfiles.envVars(), [ + "RUNFILES_DIR": runfilesDir.path, + ]) + } + + func testFailsToCreateManifestBasedBecauseManifestDoesNotExist() { + XCTAssertNil(try? Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: ["RUNFILES_MANIFEST_FILE": "non-existing path"] + )) + } + + func testFailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined() throws { + + // Third case: Only TEST_SRCDIR is present + XCTAssertNil(try? Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: ["TEST_SRCDIR": "always ignored"] + )) + + // Fourth case: Environment variables are not related to runfiles + XCTAssertNil(try? Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: ["FOO": "bar"] + )) + } + + func testManifestBasedRlocation() throws { + let manifestContents = """ + /Foo/runfile1 + Foo/runfile2 /Actual Path/runfile2 + Foo/Bar/runfile3 /the path/run file 3.txt + Foo/Bar/Dir /Actual Path/Directory + """ + let (manifest, clean) = try createMockFile(name: "MANIFEST", contents: manifestContents) + defer { try? clean() } + + + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", + ] + ) + + XCTAssertEqual(runfiles.rlocation("/Foo/runfile1")?.path, "/Foo/runfile1") + XCTAssertEqual(runfiles.rlocation("Foo/runfile2")?.path, "/Actual Path/runfile2") + XCTAssertEqual(runfiles.rlocation("Foo/Bar/runfile3")?.path, "/the path/run file 3.txt") + XCTAssertEqual(runfiles.rlocation("Foo/Bar/Dir/runfile4")?.path, "/Actual Path/Directory/runfile4") + XCTAssertEqual(runfiles.rlocation("Foo/Bar/Dir/Deeply/Nested/runfile4")?.path, "/Actual Path/Directory/Deeply/Nested/runfile4") + XCTAssertNil(runfiles.rlocation("unknown")) + + XCTAssertEqual(runfiles.rlocation("/foo")?.path, "/foo") + } + + func testManifestBasedRlocationWithRepoMappingFromMain() throws { + let repoMappingContents = """ + ,config.json,config.json~1.2.3 + ,my_module,_main + ,my_protobuf,protobuf~3.19.2 + ,my_workspace,_main + protobuf~3.19.2,config.json,config.json~1.2.3 + protobuf~3.19.2,protobuf,protobuf~3.19.2 + """ + let (repoMapping, cleanRepoMapping) = try createMockFile(name: "_repo_mapping", contents: repoMappingContents) + defer { try? cleanRepoMapping() } + + let manifestContents = """ + _repo_mapping \(repoMapping.path) + config.json /etc/config.json + protobuf~3.19.2/foo/runfile /Actual Path/protobuf/runfile + _main/bar/runfile /the/path/./to/other//other runfile.txt + protobuf~3.19.2/bar/dir /Actual Path/Directory + """ + let (manifest, cleanManifest) = try createMockFile(name: "MANIFEST", contents: manifestContents) + defer { try? cleanManifest() } + + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", + ] + ) + + XCTAssertEqual(runfiles.rlocation("my_module/bar/runfile", sourceRepository: "")?.path, "/the/path/./to/other//other runfile.txt") + XCTAssertEqual(runfiles.rlocation("my_workspace/bar/runfile", sourceRepository: "")?.path, "/the/path/./to/other//other runfile.txt") + XCTAssertEqual(runfiles.rlocation("my_protobuf/foo/runfile", sourceRepository: "")?.path, "/Actual Path/protobuf/runfile") + XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir", sourceRepository: "")?.path, "/Actual Path/Directory") + XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/file", sourceRepository: "")?.path, "/Actual Path/Directory/file") + XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le", sourceRepository: "")?.path, "/Actual Path/Directory/de eply/nes ted/fi~le") + + XCTAssertNil(runfiles.rlocation("protobuf/foo/runfile")) + XCTAssertNil(runfiles.rlocation("protobuf/bar/dir")) + XCTAssertNil(runfiles.rlocation("protobuf/bar/dir/file")) + XCTAssertNil(runfiles.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le")) + + XCTAssertEqual(runfiles.rlocation("_main/bar/runfile")?.path, "/the/path/./to/other//other runfile.txt") + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, "/Actual Path/protobuf/runfile") + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, "/Actual Path/Directory") + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, "/Actual Path/Directory/file") + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, "/Actual Path/Directory/de eply/nes ted/fi~le") + + XCTAssertEqual(runfiles.rlocation("config.json")?.path, "/etc/config.json") + XCTAssertNil(runfiles.rlocation("_main")) + XCTAssertNil(runfiles.rlocation("my_module")) + XCTAssertNil(runfiles.rlocation("protobuf")) + } + + func testManifestBasedRlocationWithRepoMappingFromOtherRepo() throws { + let repoMappingContents = """ + ,config.json,config.json~1.2.3 + ,my_module,_main + ,my_protobuf,protobuf~3.19.2 + ,my_workspace,_main + protobuf~3.19.2,config.json,config.json~1.2.3 + protobuf~3.19.2,protobuf,protobuf~3.19.2 + """ + + let (repoMapping, cleanRepoMapping) = try createMockFile(name: "_repo_mapping", contents: repoMappingContents) + defer { try? cleanRepoMapping() } + + let manifestContents = """ + _repo_mapping \(repoMapping.path) + config.json /etc/config.json + protobuf~3.19.2/foo/runfile /Actual Path/protobuf/runfile + _main/bar/runfile /the/path/./to/other//other runfile.txt + protobuf~3.19.2/bar/dir /Actual Path/Directory + """ + let (manifest, cleanManifest) = try createMockFile(name: "mock_manifest", contents: manifestContents) + defer { try? cleanManifest() } + + let runfiles = try Runfiles.create( + sourceRepository: "protobuf~3.19.2", + environment: [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", + ] + ) + + XCTAssertEqual(runfiles.rlocation("protobuf/foo/runfile")?.path, "/Actual Path/protobuf/runfile") + XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir")?.path, "/Actual Path/Directory") + XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/file")?.path, "/Actual Path/Directory/file") + XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/de eply/nes ted/fi~le")?.path, "/Actual Path/Directory/de eply/nes ted/fi~le") + + XCTAssertNil(runfiles.rlocation("my_module/bar/runfile")) + XCTAssertNil(runfiles.rlocation("my_protobuf/foo/runfile")) + XCTAssertNil(runfiles.rlocation("my_protobuf/bar/dir")) + XCTAssertNil(runfiles.rlocation("my_protobuf/bar/dir/file")) + XCTAssertNil(runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")) + + XCTAssertEqual(runfiles.rlocation("_main/bar/runfile")?.path, "/the/path/./to/other//other runfile.txt") + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, "/Actual Path/protobuf/runfile") + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, "/Actual Path/Directory") + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, "/Actual Path/Directory/file") + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, "/Actual Path/Directory/de eply/nes ted/fi~le") + + XCTAssertEqual(runfiles.rlocation("config.json")?.path, "/etc/config.json") + XCTAssertNil(runfiles.rlocation("_main")) + XCTAssertNil(runfiles.rlocation("my_module")) + XCTAssertNil(runfiles.rlocation("protobuf")) + } + + func testDirectoryBasedRlocation() throws { + let (runfilesDir, clean) = try createMockDirectory(name: "runfiles_dir") + defer { try? clean() } + + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_DIR": runfilesDir.path, + ] + ) + + XCTAssertEqual(runfiles.rlocation("arg")?.path, runfilesDir.appendingPathComponent("arg").path) + XCTAssertEqual(runfiles.rlocation("/foo")?.path, "/foo") + } + + + func testDirectoryBasedRlocationWithRepoMappingFromMain() throws { + let repoMappingContents = """ + _,config.json,config.json~1.2.3 + ,my_module,_main + ,my_protobuf,protobuf~3.19.2 + ,my_workspace,_main + protobuf~3.19.2,config.json,config.json~1.2.3 + protobuf~3.19.2,protobuf,protobuf~3.19.2 + """ + let (runfilesDir, clean) = try createMockDirectory(name: "runfiles_dir") + defer { try? clean() } + + let repoMappingFile = runfilesDir.appendingPathComponent("_repo_mapping") + try repoMappingContents.write(to: repoMappingFile, atomically: true, encoding: .utf8) + defer { try? FileManager.default.removeItem(at: repoMappingFile) } + + let runfiles = try Runfiles.create( + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_DIR": runfilesDir.path + ] + ) + + XCTAssertEqual(runfiles.rlocation("my_module/bar/runfile")?.path, runfilesDir.appendingPathComponent("_main/bar/runfile").path) + XCTAssertEqual(runfiles.rlocation("my_workspace/bar/runfile")?.path, runfilesDir.appendingPathComponent("_main/bar/runfile").path) + XCTAssertEqual(runfiles.rlocation("my_protobuf/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path) + XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path) + XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/file")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path) + XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path) + + XCTAssertEqual(runfiles.rlocation("protobuf/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf/foo/runfile").path) + XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf/bar/dir/dir/de eply/nes ted/fi~le").path) + + XCTAssertEqual(runfiles.rlocation("_main/bar/runfile")?.path, runfilesDir.appendingPathComponent("_main/bar/runfile").path) + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path) + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path) + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path) + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path) + + XCTAssertEqual(runfiles.rlocation("config.json")?.path, runfilesDir.appendingPathComponent("config.json").path) + } + + + func testDirectoryBasedRlocationWithRepoMappingFromOtherRepo() throws { + let repoMappingContents = """ + _,config.json,config.json~1.2.3 + ,my_module,_main + ,my_protobuf,protobuf~3.19.2 + ,my_workspace,_main + protobuf~3.19.2,config.json,config.json~1.2.3 + protobuf~3.19.2,protobuf,protobuf~3.19.2 + """ + let (runfilesDir, clean) = try createMockDirectory(name: "runfiles_dir") + defer { try? clean() } + + let repoMappingFile = runfilesDir.appendingPathComponent("_repo_mapping") + try repoMappingContents.write(to: repoMappingFile, atomically: true, encoding: .utf8) + defer { try? FileManager.default.removeItem(at: repoMappingFile) } + + let runfiles = try Runfiles.create( + sourceRepository: "protobuf~3.19.2", + environment: [ + "RUNFILES_DIR": runfilesDir.path + ] + ) + + XCTAssertEqual(runfiles.rlocation("protobuf/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path) + XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path) + XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/file")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path) + XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path) + + XCTAssertEqual(runfiles.rlocation("my_module/bar/runfile")?.path, runfilesDir.appendingPathComponent("my_module/bar/runfile").path) + XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("my_protobuf/bar/dir/de eply/nes ted/fi~le").path) + + XCTAssertEqual(runfiles.rlocation("_main/bar/runfile")?.path, runfilesDir.appendingPathComponent("_main/bar/runfile").path) + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path) + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path) + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path) + XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path) + + XCTAssertEqual(runfiles.rlocation("config.json")?.path, runfilesDir.appendingPathComponent("config.json").path) + } + +} + +extension String: Error {} + +func createMockFile(name: String, contents: String) throws -> (URL, () throws -> Void) { + + guard let tmpBaseDirectory = ProcessInfo.processInfo.environment["TEST_TMPDIR"] else { + XCTFail() + throw "fail" + } + + let fallbackTempDirectory = URL(filePath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) + let tempDirectory = URL(filePath: tmpBaseDirectory).appendingPathComponent(fallbackTempDirectory.lastPathComponent) + let tempFile = tempDirectory.appendingPathComponent(name) + + try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true) + try contents.write(to: tempFile, atomically: true, encoding: .utf8) + + return (tempFile, { + try FileManager.default.removeItem(at: tempFile) + try FileManager.default.removeItem(at: tempDirectory) + }) +} + +func createMockDirectory(name: String) throws -> (URL, () throws -> Void) { + guard let tmpBaseDirectory = ProcessInfo.processInfo.environment["TEST_TMPDIR"] else { + XCTFail() + throw "fail" + } + + let fallbackTempDirectory = URL(filePath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) + let tempDirectory = URL(filePath: tmpBaseDirectory).appendingPathComponent(fallbackTempDirectory.lastPathComponent) + + try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true) + + return (tempDirectory, { + try FileManager.default.removeItem(at: tempDirectory) + }) +} \ No newline at end of file From 862e2638a174ac9220edfb49fb9e4709b2702d48 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 17 Sep 2024 20:14:16 +0200 Subject: [PATCH 06/20] mutualize runfiles bzl --- swift/internal/runfiles.bzl | 19 +++++++++++++++++++ swift/swift_library.bzl | 14 ++------------ swift/swift_test.bzl | 4 ++++ 3 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 swift/internal/runfiles.bzl diff --git a/swift/internal/runfiles.bzl b/swift/internal/runfiles.bzl new file mode 100644 index 000000000..1303dc51e --- /dev/null +++ b/swift/internal/runfiles.bzl @@ -0,0 +1,19 @@ +def include_runfiles_constants(label, actions, all_deps): + """ + test + """ + for dep in all_deps: + print(dep.label) + matches = [dep for dep in all_deps if dep.label == Label("@build_bazel_rules_swift//swift/runfiles:runfiles")] + if len(matches) > 0: + repo_name_file = actions.declare_file("Runfiles+Constants.swift") + actions.write( + output = repo_name_file, + content = """ + internal enum BazelRunfilesConstants {{ + static let currentRepository = "{}" + }} + """.format(label.workspace_name), + ) + return [repo_name_file] + return [] \ No newline at end of file diff --git a/swift/swift_library.bzl b/swift/swift_library.bzl index 367ff0a4c..c83f46735 100644 --- a/swift/swift_library.bzl +++ b/swift/swift_library.bzl @@ -43,6 +43,7 @@ load( "get_providers", "include_developer_search_paths", ) +load("//swift/internal:runfiles.bzl", "include_runfiles_constants") load(":providers.bzl", "SwiftCompilerPluginInfo", "SwiftInfo") load(":swift_clang_module_aspect.bzl", "swift_clang_module_aspect") load(":swift_common.bzl", "swift_common") @@ -111,18 +112,7 @@ def _swift_library_impl(ctx): linkopts = expand_make_variables(ctx, linkopts, "linkopts") srcs = ctx.files.srcs - matches = [dep for dep in ctx.attr.deps if dep.label == Label("@@rules_swift~//swift/runfiles:runfiles")] - if len(matches) > 0: - repo_name_file = ctx.actions.declare_file("RunfilesRepoName.swift") - ctx.actions.write( - output=repo_name_file, - content=""" - internal enum BazelRunfilesConstants {{ - static let repoName = "{}" - }} - """.format(ctx.label.workspace_name), - ) - srcs = srcs + [repo_name_file] + srcs = srcs + include_runfiles_constants(ctx.label, ctx.actions, ctx.attr.deps) module_copts = additional_per_module_swiftcopts( ctx.label, diff --git a/swift/swift_test.bzl b/swift/swift_test.bzl index 2cb603d6d..15cb91efc 100644 --- a/swift/swift_test.bzl +++ b/swift/swift_test.bzl @@ -42,6 +42,7 @@ load( "expand_locations", "include_developer_search_paths", ) +load("//swift/internal:runfiles.bzl", "include_runfiles_constants") load( ":providers.bzl", "SwiftCompilerPluginInfo", @@ -390,6 +391,9 @@ def _swift_test_impl(ctx): all_supplemental_outputs = [] if srcs: + + srcs = srcs + include_runfiles_constants(ctx.label, ctx.actions, ctx.attr.deps) + # If the `swift_test` target had sources, compile those first and then # extract a symbol graph from it. compile_result = _do_compile( From 25dc9040e0c086747cc0be55b59685e965ca755d Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 17 Sep 2024 23:04:17 +0200 Subject: [PATCH 07/20] add examples --- examples/runfiles/BUILD | 13 +++++++++++++ examples/runfiles/data/sample.txt | 1 + examples/runfiles/main.swift | 17 +++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 examples/runfiles/BUILD create mode 100644 examples/runfiles/data/sample.txt create mode 100644 examples/runfiles/main.swift diff --git a/examples/runfiles/BUILD b/examples/runfiles/BUILD new file mode 100644 index 000000000..8d0627973 --- /dev/null +++ b/examples/runfiles/BUILD @@ -0,0 +1,13 @@ +load("//swift:swift.bzl", "swift_binary") + +swift_binary( + name = "binary", + srcs = ["main.swift"], + deps = [ + "//swift/runfiles", + ], + data = [ + "data/sample.txt", + ], + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/examples/runfiles/data/sample.txt b/examples/runfiles/data/sample.txt new file mode 100644 index 000000000..e021b42d9 --- /dev/null +++ b/examples/runfiles/data/sample.txt @@ -0,0 +1 @@ +Hello runfiles \ No newline at end of file diff --git a/examples/runfiles/main.swift b/examples/runfiles/main.swift new file mode 100644 index 000000000..efb0cd7d8 --- /dev/null +++ b/examples/runfiles/main.swift @@ -0,0 +1,17 @@ +import BazelRunfiles + +let runfiles = try Runfiles.create(sourceRepository: BazelRunfilesConstants.currentRepository) + +// Runfiles lookup paths have the form `my_workspace/package/file`. +// Runfiles path lookup may return nil. +guard let runFile = runfiles.rlocation("build_bazel_rules_swift/examples/runfiles/data/sample.txt") else { + fatalError("couldn't resolve runfile") +} + +print(runFile) + +// Runfiles path lookup may return a non-existent path. +let content = try String(contentsOf: runFile, encoding: .utf8) + +assert(content == "Hello runfiles") +print(content) \ No newline at end of file From 0933c6676c6487ecef0f99d362af930c59dacb61 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 17 Sep 2024 23:04:30 +0200 Subject: [PATCH 08/20] remove print --- swift/runfiles/Runfiles.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/swift/runfiles/Runfiles.swift b/swift/runfiles/Runfiles.swift index 10a25b7d8..b2945868f 100644 --- a/swift/runfiles/Runfiles.swift +++ b/swift/runfiles/Runfiles.swift @@ -162,7 +162,6 @@ public final class Runfiles { let components = path.split(separator: "/", maxSplits: 1) let targetRepository = String(components[0]) let key = RepoMappingKey(sourceRepoCanonicalName: sourceRepository, targetRepoApparentName: targetRepository) - print("corentin", key) if components.count == 1 || repoMapping[key] == nil { // One of the following is the case: From df79d83c7b798972cfb17fdbb6f9ae567b1eecfc Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 17 Sep 2024 23:04:40 +0200 Subject: [PATCH 09/20] add runfiles includes to swift-binary --- swift/swift_binary.bzl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/swift/swift_binary.bzl b/swift/swift_binary.bzl index 567ac5f71..150542d5e 100644 --- a/swift/swift_binary.bzl +++ b/swift/swift_binary.bzl @@ -37,6 +37,7 @@ load( "get_providers", "include_developer_search_paths", ) +load("//swift/internal:runfiles.bzl", "include_runfiles_constants") load(":providers.bzl", "SwiftCompilerPluginInfo", "SwiftInfo") load(":swift_common.bzl", "swift_common") @@ -69,7 +70,7 @@ def _swift_binary_impl(ctx): unsupported_features = ctx.disabled_features, ) - srcs = ctx.files.srcs + srcs = ctx.files.srcs + include_runfiles_constants(ctx.label, ctx.actions, ctx.attr.deps) output_groups = {} module_contexts = [] additional_linking_contexts = [] From ad99a6801a4cba0ed09e8a9ac43e18be6e00e7dd Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 17 Sep 2024 23:04:49 +0200 Subject: [PATCH 10/20] Add documentation --- swift/runfiles/README.md | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 swift/runfiles/README.md diff --git a/swift/runfiles/README.md b/swift/runfiles/README.md new file mode 100644 index 000000000..719e74fb0 --- /dev/null +++ b/swift/runfiles/README.md @@ -0,0 +1,74 @@ +# Swift BazelRunfiles library + +This is a Bazel Runfiles lookup library for Bazel-built Swift binaries and tests. + +Learn about runfiles: read [Runfiles guide](https://bazel.build/extending/rules#runfiles) +or watch [Fabian's BazelCon talk](https://www.youtube.com/watch?v=5NbgUMH1OGo). + +## Usage + +1. Depend on this runfiles library from your build rule: + +```python +swift_binary( + name = "my_binary", + ... + data = ["//path/to/my/data.txt"], + deps = ["@bazel_swift//swift/runfiles"], +) +``` + +2. Include the runfiles library: + +```swift +import BazelRunfiles +``` + +3. Create a Runfiles instance and use `rlocation` to look up runfile urls: + +```swift +import BazelRunfiles + +let runfiles = try? Runfiles.create(sourceRepository: BazelRunfilesConstants.currentRepository) + +let fileURL = runfiles?.rlocation("my_workspace/path/to/my/data.txt") +``` + +> The code above creates a manifest- or directory-based implementation based on + the environment variables in `Process.processInfo.environment`. + See `Runfiles.create()` for more info. + +> The Runfiles.create function uses the runfiles manifest and the runfiles + directory from the RUNFILES_MANIFEST_FILE and RUNFILES_DIR environment + variables. If not present, the function looks for the manifest and directory + near CommandLine.arguments.first (argv[0]), the path of the main program. + +> The BazelRunfilesConstants.currentRepository symbol is available in every + target that depends on the runfiles library. + +If you want to start subprocesses, and the subprocess can't automatically +find the correct runfiles directory, you can explicitly set the right +environment variables for them: + +```swift +import Foundation +import BazelRunfiles + +let runfiles = try? Runfiles.create(sourceRepository: BazelRunfilesConstant.currentRepository) + +guard let executableURL = runfiles.rlocation("my_workspace/path/to/binary") else { + return +} + +let process = Process() +process.executableURL = executableURL +process.environment = runfiles.envVars() + +do { + // Launch the process + try process.run() + process.waitUntilExit() +} catch { + // ... +} +``` From d8b2ff7cf37909f99b471584114da9f4c8ee8520 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 17 Sep 2024 23:38:53 +0200 Subject: [PATCH 11/20] add todo --- swift/internal/runfiles.bzl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/swift/internal/runfiles.bzl b/swift/internal/runfiles.bzl index 1303dc51e..ceba671ab 100644 --- a/swift/internal/runfiles.bzl +++ b/swift/internal/runfiles.bzl @@ -1,9 +1,7 @@ def include_runfiles_constants(label, actions, all_deps): """ - test + TODO: Do this the right way. """ - for dep in all_deps: - print(dep.label) matches = [dep for dep in all_deps if dep.label == Label("@build_bazel_rules_swift//swift/runfiles:runfiles")] if len(matches) > 0: repo_name_file = actions.declare_file("Runfiles+Constants.swift") From 7c102f852f8bd0966c88d9e98016fb0fcf307df9 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Tue, 17 Sep 2024 23:39:09 +0200 Subject: [PATCH 12/20] add proper throwable error enums --- swift/runfiles/Runfiles.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/swift/runfiles/Runfiles.swift b/swift/runfiles/Runfiles.swift index b2945868f..cd8ffc243 100644 --- a/swift/runfiles/Runfiles.swift +++ b/swift/runfiles/Runfiles.swift @@ -94,7 +94,7 @@ struct ManifestBased: LookupStrategy { static func loadRunfiles(from manifestPath: URL) throws -> [String: String] { guard let fileHandle = try? FileHandle(forReadingFrom: manifestPath) else { - throw RunfilesError.error + throw RunfilesError.missingRepoMapping } defer { try? fileHandle.close() @@ -123,7 +123,10 @@ struct RepoMappingKey: Hashable { } public enum RunfilesError: Error { - case error + case missingArgv0 + case missingRepoMapping + case invalidRepoMappingEntry(line: String) + case failedToFindRunfilesDirectory } public final class Runfiles { @@ -234,7 +237,7 @@ func parseRepoMapping(path: URL) throws -> [RepoMappingKey: String] { for line in lines { let fields = line.components(separatedBy: ",") if fields.count != 3 { - throw RunfilesError.error + throw RunfilesError.invalidRepoMappingEntry(line: String(line)) } let key = RepoMappingKey( sourceRepoCanonicalName: fields[0], @@ -266,7 +269,7 @@ func findRunfilesDir(environment: [String: String]) throws -> URL { // Consume the first argument (argv[0]) guard let execPath = CommandLine.arguments.first else { - throw RunfilesError.error + throw RunfilesError.missingArgv0 } var binaryPath = URL(filePath: execPath) @@ -301,5 +304,5 @@ func findRunfilesDir(environment: [String: String]) throws -> URL { } } - throw RunfilesError.error + throw RunfilesError.failedToFindRunfilesDirectory } From 6b269e4d1b9315eb98bb67f9f607f62b1bd4838d Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 18 Sep 2024 13:00:26 +0200 Subject: [PATCH 13/20] format --- test/runfiles/RunfilesTests.swift | 262 ++++++++++++++++++++---------- 1 file changed, 180 insertions(+), 82 deletions(-) diff --git a/test/runfiles/RunfilesTests.swift b/test/runfiles/RunfilesTests.swift index 48844000e..1cd68533d 100644 --- a/test/runfiles/RunfilesTests.swift +++ b/test/runfiles/RunfilesTests.swift @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import XCTest import BazelRunfiles import Foundation +import XCTest // Mainly adapted from https://github.com/bazelbuild/rules_python/blob/main/tests/runfiles/runfiles_test.py final class RunfilesTests: XCTestCase { @@ -26,9 +26,9 @@ final class RunfilesTests: XCTestCase { let runfiles = try Runfiles.create( sourceRepository: BazelRunfilesConstants.currentRepository, environment: [ - "RUNFILES_MANIFEST_FILE": fileURL.path, - "RUNFILES_DIR": "ignored when RUNFILES_MANIFEST_FILE has a value", - "TEST_SRCDIR": "always ignored", + "RUNFILES_MANIFEST_FILE": fileURL.path, + "RUNFILES_DIR": "ignored when RUNFILES_MANIFEST_FILE has a value", + "TEST_SRCDIR": "always ignored", ] ) XCTAssertEqual(runfiles.rlocation("a/b")?.path, "/c/d") @@ -42,8 +42,8 @@ final class RunfilesTests: XCTestCase { let runfiles = try Runfiles.create( sourceRepository: BazelRunfilesConstants.currentRepository, environment: [ - "RUNFILES_MANIFEST_FILE": manifest.path, - "TEST_SRCDIR": "always ignored", + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", ] ) @@ -54,13 +54,15 @@ final class RunfilesTests: XCTestCase { } func testManifestBasedRunfilesEnvVarsFromRunfilesManifest() throws { - let (manifest, clean) = try createMockFile(name: "foo.runfiles_manifest", contents: "a/b /c/d"); defer { try? clean() } + let (manifest, clean) = try createMockFile(name: "foo.runfiles_manifest", contents: "a/b /c/d"); defer { + try? clean() + } let runfiles = try Runfiles.create( sourceRepository: BazelRunfilesConstants.currentRepository, environment: [ - "RUNFILES_MANIFEST_FILE": manifest.path, - "TEST_SRCDIR": "always ignored", + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", ] ) @@ -77,8 +79,8 @@ final class RunfilesTests: XCTestCase { let runfiles = try Runfiles.create( sourceRepository: BazelRunfilesConstants.currentRepository, environment: [ - "RUNFILES_MANIFEST_FILE": manifest.path, - "TEST_SRCDIR": "always ignored", + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", ] ) @@ -86,7 +88,7 @@ final class RunfilesTests: XCTestCase { "RUNFILES_MANIFEST_FILE": manifest.path, "RUNFILES_DIR": "", ]) - + } func testCreatesDirectoryBasedRunfiles() throws { @@ -95,8 +97,8 @@ final class RunfilesTests: XCTestCase { let runfiles = try Runfiles.create( sourceRepository: BazelRunfilesConstants.currentRepository, environment: [ - "RUNFILES_DIR": runfilesDir.path, - "TEST_SRCDIR": "always ignored", + "RUNFILES_DIR": runfilesDir.path, + "TEST_SRCDIR": "always ignored", ] ) @@ -110,8 +112,8 @@ final class RunfilesTests: XCTestCase { let runfiles = try Runfiles.create( sourceRepository: BazelRunfilesConstants.currentRepository, environment: [ - "RUNFILES_DIR": runfilesDir.path, - "TEST_SRCDIR": "always ignored", + "RUNFILES_DIR": runfilesDir.path, + "TEST_SRCDIR": "always ignored", ] ) @@ -122,8 +124,8 @@ final class RunfilesTests: XCTestCase { func testFailsToCreateManifestBasedBecauseManifestDoesNotExist() { XCTAssertNil(try? Runfiles.create( - sourceRepository: BazelRunfilesConstants.currentRepository, - environment: ["RUNFILES_MANIFEST_FILE": "non-existing path"] + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: ["RUNFILES_MANIFEST_FILE": "non-existing path"] )) } @@ -131,14 +133,14 @@ final class RunfilesTests: XCTestCase { // Third case: Only TEST_SRCDIR is present XCTAssertNil(try? Runfiles.create( - sourceRepository: BazelRunfilesConstants.currentRepository, - environment: ["TEST_SRCDIR": "always ignored"] + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: ["TEST_SRCDIR": "always ignored"] )) // Fourth case: Environment variables are not related to runfiles XCTAssertNil(try? Runfiles.create( - sourceRepository: BazelRunfilesConstants.currentRepository, - environment: ["FOO": "bar"] + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: ["FOO": "bar"] )) } @@ -152,12 +154,11 @@ final class RunfilesTests: XCTestCase { let (manifest, clean) = try createMockFile(name: "MANIFEST", contents: manifestContents) defer { try? clean() } - let runfiles = try Runfiles.create( sourceRepository: BazelRunfilesConstants.currentRepository, environment: [ - "RUNFILES_MANIFEST_FILE": manifest.path, - "TEST_SRCDIR": "always ignored", + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", ] ) @@ -165,7 +166,10 @@ final class RunfilesTests: XCTestCase { XCTAssertEqual(runfiles.rlocation("Foo/runfile2")?.path, "/Actual Path/runfile2") XCTAssertEqual(runfiles.rlocation("Foo/Bar/runfile3")?.path, "/the path/run file 3.txt") XCTAssertEqual(runfiles.rlocation("Foo/Bar/Dir/runfile4")?.path, "/Actual Path/Directory/runfile4") - XCTAssertEqual(runfiles.rlocation("Foo/Bar/Dir/Deeply/Nested/runfile4")?.path, "/Actual Path/Directory/Deeply/Nested/runfile4") + XCTAssertEqual( + runfiles.rlocation("Foo/Bar/Dir/Deeply/Nested/runfile4")?.path, + "/Actual Path/Directory/Deeply/Nested/runfile4" + ) XCTAssertNil(runfiles.rlocation("unknown")) XCTAssertEqual(runfiles.rlocation("/foo")?.path, "/foo") @@ -196,17 +200,32 @@ final class RunfilesTests: XCTestCase { let runfiles = try Runfiles.create( sourceRepository: BazelRunfilesConstants.currentRepository, environment: [ - "RUNFILES_MANIFEST_FILE": manifest.path, - "TEST_SRCDIR": "always ignored", + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", ] ) - XCTAssertEqual(runfiles.rlocation("my_module/bar/runfile", sourceRepository: "")?.path, "/the/path/./to/other//other runfile.txt") - XCTAssertEqual(runfiles.rlocation("my_workspace/bar/runfile", sourceRepository: "")?.path, "/the/path/./to/other//other runfile.txt") - XCTAssertEqual(runfiles.rlocation("my_protobuf/foo/runfile", sourceRepository: "")?.path, "/Actual Path/protobuf/runfile") + XCTAssertEqual( + runfiles.rlocation("my_module/bar/runfile", sourceRepository: "")?.path, + "/the/path/./to/other//other runfile.txt" + ) + XCTAssertEqual( + runfiles.rlocation("my_workspace/bar/runfile", sourceRepository: "")?.path, + "/the/path/./to/other//other runfile.txt" + ) + XCTAssertEqual( + runfiles.rlocation("my_protobuf/foo/runfile", sourceRepository: "")?.path, + "/Actual Path/protobuf/runfile" + ) XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir", sourceRepository: "")?.path, "/Actual Path/Directory") - XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/file", sourceRepository: "")?.path, "/Actual Path/Directory/file") - XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le", sourceRepository: "")?.path, "/Actual Path/Directory/de eply/nes ted/fi~le") + XCTAssertEqual( + runfiles.rlocation("my_protobuf/bar/dir/file", sourceRepository: "")?.path, + "/Actual Path/Directory/file" + ) + XCTAssertEqual( + runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le", sourceRepository: "")?.path, + "/Actual Path/Directory/de eply/nes ted/fi~le" + ) XCTAssertNil(runfiles.rlocation("protobuf/foo/runfile")) XCTAssertNil(runfiles.rlocation("protobuf/bar/dir")) @@ -217,7 +236,10 @@ final class RunfilesTests: XCTestCase { XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, "/Actual Path/protobuf/runfile") XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, "/Actual Path/Directory") XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, "/Actual Path/Directory/file") - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, "/Actual Path/Directory/de eply/nes ted/fi~le") + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, + "/Actual Path/Directory/de eply/nes ted/fi~le" + ) XCTAssertEqual(runfiles.rlocation("config.json")?.path, "/etc/config.json") XCTAssertNil(runfiles.rlocation("_main")) @@ -249,17 +271,20 @@ final class RunfilesTests: XCTestCase { defer { try? cleanManifest() } let runfiles = try Runfiles.create( - sourceRepository: "protobuf~3.19.2", - environment: [ - "RUNFILES_MANIFEST_FILE": manifest.path, - "TEST_SRCDIR": "always ignored", - ] + sourceRepository: "protobuf~3.19.2", + environment: [ + "RUNFILES_MANIFEST_FILE": manifest.path, + "TEST_SRCDIR": "always ignored", + ] ) XCTAssertEqual(runfiles.rlocation("protobuf/foo/runfile")?.path, "/Actual Path/protobuf/runfile") XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir")?.path, "/Actual Path/Directory") XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/file")?.path, "/Actual Path/Directory/file") - XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/de eply/nes ted/fi~le")?.path, "/Actual Path/Directory/de eply/nes ted/fi~le") + XCTAssertEqual( + runfiles.rlocation("protobuf/bar/dir/de eply/nes ted/fi~le")?.path, + "/Actual Path/Directory/de eply/nes ted/fi~le" + ) XCTAssertNil(runfiles.rlocation("my_module/bar/runfile")) XCTAssertNil(runfiles.rlocation("my_protobuf/foo/runfile")) @@ -271,7 +296,10 @@ final class RunfilesTests: XCTestCase { XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, "/Actual Path/protobuf/runfile") XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, "/Actual Path/Directory") XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, "/Actual Path/Directory/file") - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, "/Actual Path/Directory/de eply/nes ted/fi~le") + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, + "/Actual Path/Directory/de eply/nes ted/fi~le" + ) XCTAssertEqual(runfiles.rlocation("config.json")?.path, "/etc/config.json") XCTAssertNil(runfiles.rlocation("_main")) @@ -284,17 +312,16 @@ final class RunfilesTests: XCTestCase { defer { try? clean() } let runfiles = try Runfiles.create( - sourceRepository: BazelRunfilesConstants.currentRepository, - environment: [ - "RUNFILES_DIR": runfilesDir.path, - ] + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_DIR": runfilesDir.path, + ] ) XCTAssertEqual(runfiles.rlocation("arg")?.path, runfilesDir.appendingPathComponent("arg").path) XCTAssertEqual(runfiles.rlocation("/foo")?.path, "/foo") } - func testDirectoryBasedRlocationWithRepoMappingFromMain() throws { let repoMappingContents = """ _,config.json,config.json~1.2.3 @@ -312,32 +339,70 @@ final class RunfilesTests: XCTestCase { defer { try? FileManager.default.removeItem(at: repoMappingFile) } let runfiles = try Runfiles.create( - sourceRepository: BazelRunfilesConstants.currentRepository, - environment: [ - "RUNFILES_DIR": runfilesDir.path - ] + sourceRepository: BazelRunfilesConstants.currentRepository, + environment: [ + "RUNFILES_DIR": runfilesDir.path, + ] ) - XCTAssertEqual(runfiles.rlocation("my_module/bar/runfile")?.path, runfilesDir.appendingPathComponent("_main/bar/runfile").path) - XCTAssertEqual(runfiles.rlocation("my_workspace/bar/runfile")?.path, runfilesDir.appendingPathComponent("_main/bar/runfile").path) - XCTAssertEqual(runfiles.rlocation("my_protobuf/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path) - XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path) - XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/file")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path) - XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path) + XCTAssertEqual( + runfiles.rlocation("my_module/bar/runfile")?.path, + runfilesDir.appendingPathComponent("_main/bar/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("my_workspace/bar/runfile")?.path, + runfilesDir.appendingPathComponent("_main/bar/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("my_protobuf/foo/runfile")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("my_protobuf/bar/dir")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path + ) + XCTAssertEqual( + runfiles.rlocation("my_protobuf/bar/dir/file")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path + ) + XCTAssertEqual( + runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path + ) - XCTAssertEqual(runfiles.rlocation("protobuf/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf/foo/runfile").path) - XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf/bar/dir/dir/de eply/nes ted/fi~le").path) + XCTAssertEqual( + runfiles.rlocation("protobuf/foo/runfile")?.path, + runfilesDir.appendingPathComponent("protobuf/foo/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le")?.path, + runfilesDir.appendingPathComponent("protobuf/bar/dir/dir/de eply/nes ted/fi~le").path + ) - XCTAssertEqual(runfiles.rlocation("_main/bar/runfile")?.path, runfilesDir.appendingPathComponent("_main/bar/runfile").path) - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path) - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path) - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path) - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path) + XCTAssertEqual( + runfiles.rlocation("_main/bar/runfile")?.path, + runfilesDir.appendingPathComponent("_main/bar/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path + ) XCTAssertEqual(runfiles.rlocation("config.json")?.path, runfilesDir.appendingPathComponent("config.json").path) } - func testDirectoryBasedRlocationWithRepoMappingFromOtherRepo() throws { let repoMappingContents = """ _,config.json,config.json~1.2.3 @@ -355,25 +420,58 @@ final class RunfilesTests: XCTestCase { defer { try? FileManager.default.removeItem(at: repoMappingFile) } let runfiles = try Runfiles.create( - sourceRepository: "protobuf~3.19.2", - environment: [ - "RUNFILES_DIR": runfilesDir.path - ] + sourceRepository: "protobuf~3.19.2", + environment: [ + "RUNFILES_DIR": runfilesDir.path, + ] ) - XCTAssertEqual(runfiles.rlocation("protobuf/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path) - XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path) - XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/file")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path) - XCTAssertEqual(runfiles.rlocation("protobuf/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path) + XCTAssertEqual( + runfiles.rlocation("protobuf/foo/runfile")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf/bar/dir")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf/bar/dir/file")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf/bar/dir/de eply/nes ted/fi~le")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path + ) - XCTAssertEqual(runfiles.rlocation("my_module/bar/runfile")?.path, runfilesDir.appendingPathComponent("my_module/bar/runfile").path) - XCTAssertEqual(runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("my_protobuf/bar/dir/de eply/nes ted/fi~le").path) + XCTAssertEqual( + runfiles.rlocation("my_module/bar/runfile")?.path, + runfilesDir.appendingPathComponent("my_module/bar/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")?.path, + runfilesDir.appendingPathComponent("my_protobuf/bar/dir/de eply/nes ted/fi~le").path + ) - XCTAssertEqual(runfiles.rlocation("_main/bar/runfile")?.path, runfilesDir.appendingPathComponent("_main/bar/runfile").path) - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path) - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path) - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path) - XCTAssertEqual(runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path) + XCTAssertEqual( + runfiles.rlocation("_main/bar/runfile")?.path, + runfilesDir.appendingPathComponent("_main/bar/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/foo/runfile")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/foo/runfile").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/bar/dir")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/bar/dir/file")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/file").path + ) + XCTAssertEqual( + runfiles.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")?.path, + runfilesDir.appendingPathComponent("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le").path + ) XCTAssertEqual(runfiles.rlocation("config.json")?.path, runfilesDir.appendingPathComponent("config.json").path) } @@ -402,8 +500,8 @@ func createMockFile(name: String, contents: String) throws -> (URL, () throws -> }) } -func createMockDirectory(name: String) throws -> (URL, () throws -> Void) { - guard let tmpBaseDirectory = ProcessInfo.processInfo.environment["TEST_TMPDIR"] else { +func createMockDirectory(name _: String) throws -> (URL, () throws -> Void) { + guard let tmpBaseDirectory = ProcessInfo.processInfo.environment["TEST_TMPDIR"] else { XCTFail() throw "fail" } @@ -416,4 +514,4 @@ func createMockDirectory(name: String) throws -> (URL, () throws -> Void) { return (tempDirectory, { try FileManager.default.removeItem(at: tempDirectory) }) -} \ No newline at end of file +} From 189a8fc90d3deb51bfa275f8948a80643231ff70 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 18 Sep 2024 13:01:19 +0200 Subject: [PATCH 14/20] URL(filePath: does not exist in foundation for Linux --- swift/runfiles/Runfiles.swift | 16 ++++++++-------- test/runfiles/RunfilesTests.swift | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/swift/runfiles/Runfiles.swift b/swift/runfiles/Runfiles.swift index cd8ffc243..97b9ee834 100644 --- a/swift/runfiles/Runfiles.swift +++ b/swift/runfiles/Runfiles.swift @@ -27,7 +27,7 @@ struct DirectoryBased: LookupStrategy { } func rlocationChecked(path: String) -> URL? { - runfilesRoot.appending(path: path) + runfilesRoot.appendingPathComponent(path) } func envVars() -> [String: String] { @@ -49,7 +49,7 @@ struct ManifestBased: LookupStrategy { func rlocationChecked(path: String) -> URL? { if let runfile = runfiles[path] { - return URL(filePath: runfile) + return URL(fileURLWithPath: runfile) } // Search for prefixes in the path @@ -63,7 +63,7 @@ struct ManifestBased: LookupStrategy { let prefix = String(path[.. [RepoMappingKey: String] { func findRunfilesDir(environment: [String: String]) throws -> URL { if let runfilesDirPath = environment["RUNFILES_DIR"] { - let runfilesDirURL = URL(filePath: runfilesDirPath) + let runfilesDirURL = URL(fileURLWithPath: runfilesDirPath) if FileManager.default.fileExists(atPath: runfilesDirURL.path, isDirectory: nil) { return runfilesDirURL } } if let testSrcdirPath = environment["TEST_SRCDIR"] { - let testSrcdirURL = URL(filePath: testSrcdirPath) + let testSrcdirURL = URL(fileURLWithPath: testSrcdirPath) if FileManager.default.fileExists(atPath: testSrcdirURL.path, isDirectory: nil) { return testSrcdirURL } @@ -272,7 +272,7 @@ func findRunfilesDir(environment: [String: String]) throws -> URL { throw RunfilesError.missingArgv0 } - var binaryPath = URL(filePath: execPath) + var binaryPath = URL(fileURLWithPath: execPath) while true { // Check for our neighboring $binary.runfiles directory. diff --git a/test/runfiles/RunfilesTests.swift b/test/runfiles/RunfilesTests.swift index 1cd68533d..03fec0737 100644 --- a/test/runfiles/RunfilesTests.swift +++ b/test/runfiles/RunfilesTests.swift @@ -487,8 +487,8 @@ func createMockFile(name: String, contents: String) throws -> (URL, () throws -> throw "fail" } - let fallbackTempDirectory = URL(filePath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) - let tempDirectory = URL(filePath: tmpBaseDirectory).appendingPathComponent(fallbackTempDirectory.lastPathComponent) + let fallbackTempDirectory = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) + let tempDirectory = URL(fileURLWithPath: tmpBaseDirectory).appendingPathComponent(fallbackTempDirectory.lastPathComponent) let tempFile = tempDirectory.appendingPathComponent(name) try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true) @@ -506,8 +506,8 @@ func createMockDirectory(name _: String) throws -> (URL, () throws -> Void) { throw "fail" } - let fallbackTempDirectory = URL(filePath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) - let tempDirectory = URL(filePath: tmpBaseDirectory).appendingPathComponent(fallbackTempDirectory.lastPathComponent) + let fallbackTempDirectory = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) + let tempDirectory = URL(fileURLWithPath: tmpBaseDirectory).appendingPathComponent(fallbackTempDirectory.lastPathComponent) try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true) From 589c96492aa064725538257a20fe3406044b02ac Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 18 Sep 2024 13:02:18 +0200 Subject: [PATCH 15/20] Remove false test: argv0 is xctest without runfiles on macos but not on linux --- test/runfiles/RunfilesTests.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/runfiles/RunfilesTests.swift b/test/runfiles/RunfilesTests.swift index 03fec0737..0f3d48aa3 100644 --- a/test/runfiles/RunfilesTests.swift +++ b/test/runfiles/RunfilesTests.swift @@ -129,21 +129,6 @@ final class RunfilesTests: XCTestCase { )) } - func testFailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined() throws { - - // Third case: Only TEST_SRCDIR is present - XCTAssertNil(try? Runfiles.create( - sourceRepository: BazelRunfilesConstants.currentRepository, - environment: ["TEST_SRCDIR": "always ignored"] - )) - - // Fourth case: Environment variables are not related to runfiles - XCTAssertNil(try? Runfiles.create( - sourceRepository: BazelRunfilesConstants.currentRepository, - environment: ["FOO": "bar"] - )) - } - func testManifestBasedRlocation() throws { let manifestContents = """ /Foo/runfile1 From ca0cd32dbc1387f60022bc062ed192c0a39dbad1 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 18 Sep 2024 13:16:13 +0200 Subject: [PATCH 16/20] buildifier --- examples/runfiles/BUILD | 8 ++++---- swift/BUILD | 3 +++ swift/internal/BUILD | 6 ++++++ swift/internal/runfiles.bzl | 32 ++++++++++++++++++++++++++++---- swift/runfiles/BUILD | 2 +- swift/swift_test.bzl | 1 - 6 files changed, 42 insertions(+), 10 deletions(-) diff --git a/examples/runfiles/BUILD b/examples/runfiles/BUILD index 8d0627973..33b0b93bf 100644 --- a/examples/runfiles/BUILD +++ b/examples/runfiles/BUILD @@ -3,11 +3,11 @@ load("//swift:swift.bzl", "swift_binary") swift_binary( name = "binary", srcs = ["main.swift"], - deps = [ - "//swift/runfiles", - ], data = [ "data/sample.txt", ], visibility = ["//visibility:public"], -) \ No newline at end of file + deps = [ + "//swift/runfiles", + ], +) diff --git a/swift/BUILD b/swift/BUILD index 79867c352..94bddfb9c 100644 --- a/swift/BUILD +++ b/swift/BUILD @@ -83,6 +83,7 @@ bzl_library( "//swift/internal:compiling", "//swift/internal:feature_names", "//swift/internal:linking", + "//swift/internal:runfiles", "//swift/internal:toolchain_utils", "//swift/internal:utils", "@bazel_skylib//lib:paths", @@ -189,6 +190,7 @@ bzl_library( "//swift/internal:feature_names", "//swift/internal:linking", "//swift/internal:output_groups", + "//swift/internal:runfiles", "//swift/internal:toolchain_utils", "//swift/internal:utils", "@bazel_skylib//lib:dicts", @@ -252,6 +254,7 @@ bzl_library( "//swift/internal:feature_names", "//swift/internal:linking", "//swift/internal:output_groups", + "//swift/internal:runfiles", "//swift/internal:swift_symbol_graph_aspect", "//swift/internal:toolchain_utils", "//swift/internal:utils", diff --git a/swift/internal/BUILD b/swift/internal/BUILD index 8a1c4bd75..9b6d1e793 100644 --- a/swift/internal/BUILD +++ b/swift/internal/BUILD @@ -198,6 +198,12 @@ bzl_library( ], ) +bzl_library( + name = "runfiles", + srcs = ["runfiles.bzl"], + visibility = ["//swift:__subpackages__"], +) + bzl_library( name = "swift_autoconfiguration", srcs = ["swift_autoconfiguration.bzl"], diff --git a/swift/internal/runfiles.bzl b/swift/internal/runfiles.bzl index ceba671ab..d3894fa84 100644 --- a/swift/internal/runfiles.bzl +++ b/swift/internal/runfiles.bzl @@ -1,6 +1,30 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Internal API to generate constants needed by the runfiles library""" + def include_runfiles_constants(label, actions, all_deps): - """ - TODO: Do this the right way. + """TODO: Do this the right way. + + Args: + label: The label of the target for which the Swift files are being generated. + actions: The actions object used to declare the files to be generated and the actions that generate them. + all_deps: The list of public dependencies of the target. + + Returns: + A list containing the runfiles constant declared file if applicable; + otherwise an empty list. """ matches = [dep for dep in all_deps if dep.label == Label("@build_bazel_rules_swift//swift/runfiles:runfiles")] if len(matches) > 0: @@ -9,9 +33,9 @@ def include_runfiles_constants(label, actions, all_deps): output = repo_name_file, content = """ internal enum BazelRunfilesConstants {{ - static let currentRepository = "{}" + static let currentRepository = "{}" }} """.format(label.workspace_name), ) return [repo_name_file] - return [] \ No newline at end of file + return [] diff --git a/swift/runfiles/BUILD b/swift/runfiles/BUILD index c211d4cca..130d19200 100644 --- a/swift/runfiles/BUILD +++ b/swift/runfiles/BUILD @@ -7,4 +7,4 @@ swift_library( ], module_name = "BazelRunfiles", visibility = ["//visibility:public"], -) \ No newline at end of file +) diff --git a/swift/swift_test.bzl b/swift/swift_test.bzl index 15cb91efc..a040d6553 100644 --- a/swift/swift_test.bzl +++ b/swift/swift_test.bzl @@ -391,7 +391,6 @@ def _swift_test_impl(ctx): all_supplemental_outputs = [] if srcs: - srcs = srcs + include_runfiles_constants(ctx.label, ctx.actions, ctx.attr.deps) # If the `swift_test` target had sources, compile those first and then From 6c7f9937b9c7bbadf677d90e254ee6035041a63b Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 18 Sep 2024 13:37:21 +0200 Subject: [PATCH 17/20] trigger From 7383bb8413346b43039c230d801af041bbd6778d Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 25 Sep 2024 19:51:42 +0200 Subject: [PATCH 18/20] remove runfiles walk up the path discovery --- swift/runfiles/Runfiles.swift | 66 +++-------------------------------- 1 file changed, 5 insertions(+), 61 deletions(-) diff --git a/swift/runfiles/Runfiles.swift b/swift/runfiles/Runfiles.swift index 97b9ee834..a79bb5b4b 100644 --- a/swift/runfiles/Runfiles.swift +++ b/swift/runfiles/Runfiles.swift @@ -123,10 +123,9 @@ struct RepoMappingKey: Hashable { } public enum RunfilesError: Error { - case missingArgv0 + case missingEnvVars case missingRepoMapping case invalidRepoMappingEntry(line: String) - case failedToFindRunfilesDirectory } public final class Runfiles { @@ -202,8 +201,10 @@ public final class Runfiles { let strategy: LookupStrategy if let manifestFile = environment["RUNFILES_MANIFEST_FILE"] { strategy = try ManifestBased(manifestPath: URL(fileURLWithPath: manifestFile)) + } else if let runfilesDir = environment["RUNFILES_DIR"] { + strategy = DirectoryBased(path: URL(fileURLWithPath: runfilesDir)) } else { - strategy = try DirectoryBased(path: findRunfilesDir(environment: environment)) + throw RunfilesError.missingEnvVars } // If the repository mapping file can't be found, that is not an error: We @@ -248,61 +249,4 @@ func parseRepoMapping(path: URL) throws -> [RepoMappingKey: String] { } return repoMapping -} - -// MARK: Finding Runfiles Directory - -func findRunfilesDir(environment: [String: String]) throws -> URL { - if let runfilesDirPath = environment["RUNFILES_DIR"] { - let runfilesDirURL = URL(fileURLWithPath: runfilesDirPath) - if FileManager.default.fileExists(atPath: runfilesDirURL.path, isDirectory: nil) { - return runfilesDirURL - } - } - - if let testSrcdirPath = environment["TEST_SRCDIR"] { - let testSrcdirURL = URL(fileURLWithPath: testSrcdirPath) - if FileManager.default.fileExists(atPath: testSrcdirURL.path, isDirectory: nil) { - return testSrcdirURL - } - } - - // Consume the first argument (argv[0]) - guard let execPath = CommandLine.arguments.first else { - throw RunfilesError.missingArgv0 - } - - var binaryPath = URL(fileURLWithPath: execPath) - - while true { - // Check for our neighboring $binary.runfiles directory. - let runfilesName = binaryPath.lastPathComponent + ".runfiles" - let runfilesPath = binaryPath.deletingLastPathComponent().appendingPathComponent(runfilesName) - - if FileManager.default.fileExists(atPath: runfilesPath.path, isDirectory: nil) { - return runfilesPath - } - - // Check if we're already under a *.runfiles directory. - var ancestorURL = binaryPath.deletingLastPathComponent() - while ancestorURL.path != "/" { - if ancestorURL.lastPathComponent.hasSuffix(".runfiles") { - return ancestorURL - } - ancestorURL.deleteLastPathComponent() - } - - // Check if it's a symlink and follow it. - if let symlinkTarget = try? FileManager.default.destinationOfSymbolicLink(atPath: binaryPath.path) { - let linkTargetURL = URL( - fileURLWithPath: symlinkTarget, - relativeTo: binaryPath.deletingLastPathComponent() - ) - binaryPath = linkTargetURL - } else { - break - } - } - - throw RunfilesError.failedToFindRunfilesDirectory -} +} \ No newline at end of file From 480e57c7914ac0a558674b504e29444a1d331cc0 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Wed, 25 Sep 2024 19:53:01 +0200 Subject: [PATCH 19/20] fix wrong error name --- swift/runfiles/Runfiles.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swift/runfiles/Runfiles.swift b/swift/runfiles/Runfiles.swift index a79bb5b4b..f98e1546b 100644 --- a/swift/runfiles/Runfiles.swift +++ b/swift/runfiles/Runfiles.swift @@ -94,7 +94,7 @@ struct ManifestBased: LookupStrategy { static func loadRunfiles(from manifestPath: URL) throws -> [String: String] { guard let fileHandle = try? FileHandle(forReadingFrom: manifestPath) else { - throw RunfilesError.missingRepoMapping + throw RunfilesError.missingManifest } defer { try? fileHandle.close() @@ -124,7 +124,7 @@ struct RepoMappingKey: Hashable { public enum RunfilesError: Error { case missingEnvVars - case missingRepoMapping + case missingManifest case invalidRepoMappingEntry(line: String) } From 04751c608239ce847d18ebde37b71b997f90c436 Mon Sep 17 00:00:00 2001 From: Corentin Kerisit Date: Fri, 27 Sep 2024 16:25:58 +0200 Subject: [PATCH 20/20] missing new line at end of file --- swift/runfiles/Runfiles.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swift/runfiles/Runfiles.swift b/swift/runfiles/Runfiles.swift index f98e1546b..8ea61a0bd 100644 --- a/swift/runfiles/Runfiles.swift +++ b/swift/runfiles/Runfiles.swift @@ -249,4 +249,4 @@ func parseRepoMapping(path: URL) throws -> [RepoMappingKey: String] { } return repoMapping -} \ No newline at end of file +}