Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add git information to PD context #7202

Merged
merged 3 commits into from Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion Package.swift
Expand Up @@ -224,7 +224,8 @@ let package = Package(
name: "PackageLoading",
dependencies: [
"Basics",
"PackageModel"
"PackageModel",
"SourceControl",
],
exclude: ["CMakeLists.txt", "README.md"]
),
Expand Down
22 changes: 21 additions & 1 deletion Sources/PackageDescription/Context.swift
Expand Up @@ -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? {
neonichu marked this conversation as resolved.
Show resolved Hide resolved
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
Expand All @@ -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
}
11 changes: 7 additions & 4 deletions Sources/PackageLoading/ContextModel.swift
Expand Up @@ -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 {
Expand Down
19 changes: 18 additions & 1 deletion Sources/PackageLoading/ManifestLoader.swift
Expand Up @@ -15,6 +15,7 @@ import Basics
import Dispatch
import Foundation
import PackageModel
import SourceControl

import class TSCBasic.BufferedOutputByteStream
import struct TSCBasic.ByteString
Expand Down Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions Sources/PackageModel/ToolsVersion.swift
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions Sources/SourceControl/GitRepository.swift
Expand Up @@ -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.
Expand Down
91 changes: 91 additions & 0 deletions Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift
@@ -0,0 +1,91 @@
//===----------------------------------------------------------------------===//
//
// 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)
}
}
}