diff --git a/Sources/Basics/FileSystem/FileSystem+Extensions.swift b/Sources/Basics/FileSystem/FileSystem+Extensions.swift index af926941b3b..4de29e4bc70 100644 --- a/Sources/Basics/FileSystem/FileSystem+Extensions.swift +++ b/Sources/Basics/FileSystem/FileSystem+Extensions.swift @@ -19,6 +19,7 @@ import struct TSCBasic.ByteString import struct TSCBasic.FileInfo import class TSCBasic.FileLock import enum TSCBasic.FileMode +import enum TSCBasic.FileSystemAttribute import protocol TSCBasic.FileSystem import var TSCBasic.localFileSystem import protocol TSCBasic.WritableByteStream @@ -71,8 +72,8 @@ extension FileSystem { /// Returns `true` if a given path has a quarantine attribute applied if when file system supports this attribute. /// Returns `false` if such attribute is not applied or it isn't supported. - public func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool { - self.hasQuarantineAttribute(path.underlying) + public func hasAttribute(_ name: FileSystemAttribute, _ path: AbsolutePath) -> Bool { + self.hasAttribute(name, path.underlying) } /// Get the contents of the given directory, in an undefined order. diff --git a/Sources/PackageModel/Destination.swift b/Sources/PackageModel/Destination.swift index 0ec8e64626e..0415868dd20 100644 --- a/Sources/PackageModel/Destination.swift +++ b/Sources/PackageModel/Destination.swift @@ -52,6 +52,11 @@ public enum DestinationError: Swift.Error { /// A destination with this artifact ID is already installed. Can't install a new bundle with this artifact, /// installed artifact IDs are expected to be unique. case destinationArtifactAlreadyInstalled(installedBundleName: String, newBundleName: String, artifactID: String) + + #if os(macOS) + /// Quarantine attribute should be removed by the `xattr` command from an installed bundle. + case quarantineAttributePresent(bundlePath: AbsolutePath) + #endif } extension DestinationError: CustomStringConvertible { @@ -93,6 +98,17 @@ extension DestinationError: CustomStringConvertible { `\(installedBundleName)`. Can't install a new bundle `\(newBundleName)` with this artifact, artifact IDs \ are expected to be unique across all installed Swift SDK bundles. """ + #if os(macOS) + case .quarantineAttributePresent(let bundlePath): + return """ + Quarantine attribute is present on a Swift SDK bundle at path `\(bundlePath)`. If you're certain that the \ + bundle was downloaded from a trusted source, you can remove the attribute with this command: + + xattr -d -r -s com.apple.quarantine "\(bundlePath)" + + and try to install this bundle again. + """ + #endif } } } diff --git a/Sources/PackageModel/SwiftSDKBundle.swift b/Sources/PackageModel/SwiftSDKBundle.swift index 6ddbc644a36..6869a6ae449 100644 --- a/Sources/PackageModel/SwiftSDKBundle.swift +++ b/Sources/PackageModel/SwiftSDKBundle.swift @@ -13,6 +13,7 @@ import Basics import struct Foundation.URL +import protocol TSCBasic.FileSystem import struct TSCBasic.RegEx /// Represents an `.artifactbundle` on the filesystem that contains a Swift SDK. @@ -120,7 +121,7 @@ public struct SwiftSDKBundle { return selectedDestination } - + /// Installs a destination bundle from a given path or URL to a destinations installation directory. /// - Parameters: /// - bundlePathOrURL: A string passed on the command line, which is either an absolute or relative to a current @@ -245,6 +246,13 @@ public struct SwiftSDKBundle { _ archiver: some Archiver, _ observabilityScope: ObservabilityScope ) throws { + #if os(macOS) + // Check the quarantine attribute on bundles downloaded manually in the browser. + guard !fileSystem.hasAttribute(.quarantine, bundlePath) else { + throw DestinationError.quarantineAttributePresent(bundlePath: bundlePath) + } + #endif + let unpackedBundlePath = try unpackIfNeeded( bundlePath: bundlePath, destinationsDirectory: destinationsDirectory, @@ -359,7 +367,8 @@ extension ArtifactsArchiveMetadata { do { let destinations = try Destination.decode( - fromFile: variantConfigurationPath, fileSystem: fileSystem, observabilityScope: observabilityScope + fromFile: variantConfigurationPath, fileSystem: fileSystem, + observabilityScope: observabilityScope ) variants.append(.init(metadata: variantMetadata, swiftSDKs: destinations)) @@ -378,7 +387,7 @@ extension ArtifactsArchiveMetadata { } } -extension Array where Element == SwiftSDKBundle { +extension [SwiftSDKBundle] { /// Select a destination with a given artifact ID from a `self` array of available destinations. /// - Parameters: /// - id: artifact ID of the destination to look up.