From ca85d74fc6c1afc21e2528a4cdb4e0880dd73af9 Mon Sep 17 00:00:00 2001 From: Boris Buegling Date: Thu, 14 Dec 2023 13:54:38 -0800 Subject: [PATCH 1/3] Add git information to PD context This adds a few bits of information about a package's git repository to the context accessible to package manifests: - the current commit - the current tag (if any) - whether or not there are uncommited changes rdar://111523616 --- Package.swift | 3 +- Sources/PackageDescription/Context.swift | 22 ++++- Sources/PackageLoading/ContextModel.swift | 11 ++- Sources/PackageLoading/ManifestLoader.swift | 19 +++- Sources/PackageModel/ToolsVersion.swift | 1 + Sources/SourceControl/GitRepository.swift | 11 +++ .../PD_5_11_LoadingTests.swift | 88 +++++++++++++++++++ 7 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift diff --git a/Package.swift b/Package.swift index 39c6823061b..52ad8af68d0 100644 --- a/Package.swift +++ b/Package.swift @@ -224,7 +224,8 @@ let package = Package( name: "PackageLoading", dependencies: [ "Basics", - "PackageModel" + "PackageModel", + "SourceControl", ], exclude: ["CMakeLists.txt", "README.md"] ), diff --git a/Sources/PackageDescription/Context.swift b/Sources/PackageDescription/Context.swift index 612b3855529..a931d880628 100644 --- a/Sources/PackageDescription/Context.swift +++ b/Sources/PackageDescription/Context.swift @@ -22,7 +22,19 @@ public struct Context { public static var packageDirectory : String { model.packageDirectory } - + + /// Information about the git status of a given package, if available. + @available(_PackageDescription, introduced: 5.11) + public static var gitInformation: GitInformation? { + model.gitInformation.map { + GitInformation( + currentTag: $0.currentTag, + currentCommit: $0.currentCommit, + hasUncommittedChanges: $0.hasUncommittedChanges + ) + } + } + /// Snapshot of the system environment variables. public static var environment : [String : String] { model.environment @@ -31,3 +43,11 @@ public struct Context { private init() { } } + +/// Information about the git status of a given package, if available. +@available(_PackageDescription, introduced: 5.11) +public struct GitInformation { + public let currentTag: String? + public let currentCommit: String + public let hasUncommittedChanges: Bool +} diff --git a/Sources/PackageLoading/ContextModel.swift b/Sources/PackageLoading/ContextModel.swift index 90bd7b50765..c01935e76a6 100644 --- a/Sources/PackageLoading/ContextModel.swift +++ b/Sources/PackageLoading/ContextModel.swift @@ -18,14 +18,17 @@ import Foundation struct ContextModel { let packageDirectory : String - - init(packageDirectory : String) { - self.packageDirectory = packageDirectory - } + let gitInformation: GitInformation? var environment : [String : String] { ProcessInfo.processInfo.environment } + + struct GitInformation: Codable { + let currentTag: String? + let currentCommit: String + let hasUncommittedChanges: Bool + } } extension ContextModel : Codable { diff --git a/Sources/PackageLoading/ManifestLoader.swift b/Sources/PackageLoading/ManifestLoader.swift index 99bf6e161a2..9ec7dc99a98 100644 --- a/Sources/PackageLoading/ManifestLoader.swift +++ b/Sources/PackageLoading/ManifestLoader.swift @@ -15,6 +15,7 @@ import Basics import Dispatch import Foundation import PackageModel +import SourceControl import class TSCBasic.BufferedOutputByteStream import struct TSCBasic.ByteString @@ -958,7 +959,23 @@ public final class ManifestLoader: ManifestLoaderProtocol { do { let packageDirectory = manifestPath.parentDirectory.pathString - let contextModel = ContextModel(packageDirectory: packageDirectory) + + let gitInformation: ContextModel.GitInformation? + do { + let repo = GitRepository(path: manifestPath.parentDirectory) + gitInformation = ContextModel.GitInformation( + currentTag: repo.getCurrentTag(), + currentCommit: try repo.getCurrentRevision().identifier, + hasUncommittedChanges: repo.hasUncommittedChanges() + ) + } catch { + gitInformation = nil + } + + let contextModel = ContextModel( + packageDirectory: packageDirectory, + gitInformation: gitInformation + ) cmd += ["-context", try contextModel.encode()] } catch { return completion(.failure(error)) diff --git a/Sources/PackageModel/ToolsVersion.swift b/Sources/PackageModel/ToolsVersion.swift index 427399d0255..5fe51fe4bf4 100644 --- a/Sources/PackageModel/ToolsVersion.swift +++ b/Sources/PackageModel/ToolsVersion.swift @@ -31,6 +31,7 @@ public struct ToolsVersion: Equatable, Hashable, Codable, Sendable { public static let v5_8 = ToolsVersion(version: "5.8.0") public static let v5_9 = ToolsVersion(version: "5.9.0") public static let v5_10 = ToolsVersion(version: "5.10.0") + public static let v5_11 = ToolsVersion(version: "5.11.0") public static let vNext = ToolsVersion(version: "999.0.0") /// The current tools version in use. diff --git a/Sources/SourceControl/GitRepository.swift b/Sources/SourceControl/GitRepository.swift index 2781413db10..8859982421f 100644 --- a/Sources/SourceControl/GitRepository.swift +++ b/Sources/SourceControl/GitRepository.swift @@ -623,6 +623,17 @@ public final class GitRepository: Repository, WorkingCheckout { } } + public func getCurrentTag() -> String? { + self.lock.withLock { + try? callGit( + "describe", + "--exact-match", + "--tags", + failureMessage: "Couldn’t get current tag" + ) + } + } + public func checkout(tag: String) throws { // FIXME: Audit behavior with off-branch tags in remote repositories, we // may need to take a little more care here. diff --git a/Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift new file mode 100644 index 00000000000..641a2e7c831 --- /dev/null +++ b/Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageModel +import SourceControl +import SPMTestSupport +import XCTest + +class PackageDescription5_11LoadingTests: PackageDescriptionLoadingTests { + override var toolsVersion: ToolsVersion { + .v5_11 + } + + func testPackageContextGitStatus() throws { + let content = """ + import PackageDescription + let package = Package(name: "\\(Context.gitInformation?.hasUncommittedChanges == true)") + """ + + try loadRootManifestWithBasicGitRepository(manifestContent: content) { manifest, observability in + XCTAssertNoDiagnostics(observability.diagnostics) + XCTAssertEqual(manifest.displayName, "true") + } + } + + func testPackageContextGitTag() throws { + let content = """ + import PackageDescription + let package = Package(name: "\\(Context.gitInformation?.currentTag ?? "")") + """ + + try loadRootManifestWithBasicGitRepository(manifestContent: content) { manifest, observability in + XCTAssertNoDiagnostics(observability.diagnostics) + XCTAssertEqual(manifest.displayName, "lunch") + } + } + + func testPackageContextGitCommit() throws { + let content = """ + import PackageDescription + let package = Package(name: "\\(Context.gitInformation?.currentCommit ?? "")") + """ + + try loadRootManifestWithBasicGitRepository(manifestContent: content) { manifest, observability in + XCTAssertNoDiagnostics(observability.diagnostics) + + let repo = GitRepository(path: manifest.path.parentDirectory) + let currentRevision = try repo.getCurrentRevision() + XCTAssertEqual(manifest.displayName, currentRevision.identifier) + } + } + + private func loadRootManifestWithBasicGitRepository(manifestContent: String, validator: (Manifest, TestingObservability) throws -> ()) throws { + let observability = ObservabilitySystem.makeForTesting() + + try testWithTemporaryDirectory { tmpdir in + let manifestPath = tmpdir.appending(component: Manifest.filename) + try localFileSystem.writeFileContents(manifestPath, string: manifestContent) + try localFileSystem.writeFileContents(tmpdir.appending("best.txt"), string: "best") + + let repo = GitRepository(path: tmpdir) + try repo.create() + try repo.stage(file: manifestPath.pathString) + try repo.commit(message: "best") + try repo.tag(name: "lunch") + + let manifest = try manifestLoader.load( + manifestPath: manifestPath, + packageKind: .root(tmpdir), + toolsVersion: self.toolsVersion, + fileSystem: localFileSystem, + observabilityScope: observability.topScope + ) + + try validator(manifest, observability) + } + } +} From 0f87c23da084cc0dd2703f11063b24dd50031957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boris=20B=C3=BCgling?= Date: Thu, 11 Jan 2024 09:39:55 -0800 Subject: [PATCH 2/3] Update Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift Co-authored-by: Max Desiatov --- Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift index 641a2e7c831..36b4c8a6f91 100644 --- a/Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift @@ -60,7 +60,10 @@ class PackageDescription5_11LoadingTests: PackageDescriptionLoadingTests { } } - private func loadRootManifestWithBasicGitRepository(manifestContent: String, validator: (Manifest, TestingObservability) throws -> ()) throws { + private func loadRootManifestWithBasicGitRepository( + manifestContent: String, + validator: (Manifest, TestingObservability) throws -> () + ) throws { let observability = ObservabilitySystem.makeForTesting() try testWithTemporaryDirectory { tmpdir in From d47fad5dce95d7fb68c81c18ad69907955fc9c00 Mon Sep 17 00:00:00 2001 From: Boris Buegling Date: Thu, 11 Jan 2024 11:03:38 -0800 Subject: [PATCH 3/3] add to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 156587e37cd..fe5ed046fe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ Note: This is in reverse chronological order, so newer entries are added to the Swift Next ----------- +* [#7202] + + Package manifests can now access information about the Git repository the given package is in via the context object's + `gitInformation` property. This allows to determine the current tag (if any), the current commit and whether or not there are uncommited changes. + * [#7010] On macOS, `swift build` and `swift run` now produce binaries that allow backtraces in debug builds. Pass `SWIFT_BACKTRACE=enable=yes` environment variable to enable backtraces on such binaries when running them.