Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d408e5f
WIP - I made a test and it passed
mattmassicotte May 6, 2026
6f567fa
WIP - concept for actually resolving default settings
mattmassicotte May 7, 2026
19d4135
WIP - fill out public manifest API
mattmassicotte May 12, 2026
bd10172
WIP - migrate tests to dedicated file
mattmassicotte May 12, 2026
a6776b8
WIP - header search path test
mattmassicotte May 12, 2026
b99608a
WIP - store all settings in one array
mattmassicotte May 12, 2026
bfa3781
WIP - define tests
mattmassicotte May 12, 2026
e6c890f
WIP - linked lib tests
mattmassicotte May 12, 2026
e012316
WIP - linked framework tests
mattmassicotte May 12, 2026
911e698
WIP - interoperability tests
mattmassicotte May 12, 2026
c98df06
WIP - enableUpcomingFeature tests
mattmassicotte May 12, 2026
ac908f2
WIP - remove some leftover prints
mattmassicotte May 12, 2026
4c1ae70
WIP - enableExperimentalFeature tests
mattmassicotte May 12, 2026
97d259e
WIP - strictMemorySafety tests
mattmassicotte May 12, 2026
2d2670e
WIP - unsafeFlags tests
mattmassicotte May 12, 2026
39d7138
WIP - parse the rest of the default setting types
mattmassicotte May 12, 2026
fdf205a
WIP - swiftLanguageMode tests
mattmassicotte May 12, 2026
fc2c828
WIP - treatAllWarnings tests
mattmassicotte May 12, 2026
b300c59
WIP - treatWarning tests
mattmassicotte May 12, 2026
18dd205
WIP - enableWarning tests
mattmassicotte May 12, 2026
ece5e08
WIP - disableWarning tests
mattmassicotte May 12, 2026
16d0263
WIP - move defaultIsolation tests to the end
mattmassicotte May 12, 2026
80fa6ea
WIP - working on manifest loading
mattmassicotte May 12, 2026
c0631d0
WIP - fix up correctly parsing all default settings
mattmassicotte May 13, 2026
0f6abcc
WIP - experiment with unconditional overrides
mattmassicotte May 13, 2026
2899598
WIP - implement inheritance
mattmassicotte May 14, 2026
da2f568
WIP - some missing comments
mattmassicotte May 14, 2026
d68e423
WIP - introduce some subexpressions to help with parsing
mattmassicotte May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Sources/PackageLoading/ManifestJSONParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ enum ManifestJSONParser {
struct Result {
var name: String
var defaultLocalization: String?
var defaultSwiftSettings: [TargetBuildSettingDescription.Setting]? = []
var defaultCSettings: [TargetBuildSettingDescription.Setting]? = []
var defaultCXXSettings: [TargetBuildSettingDescription.Setting]? = []
var defaultLinkerSettings: [TargetBuildSettingDescription.Setting]? = []
var platforms: [PlatformDescription] = []
var targets: [TargetDescription] = []
var pkgConfig: String?
Expand Down Expand Up @@ -107,6 +111,10 @@ enum ManifestJSONParser {
return Result(
name: input.package.name,
defaultLocalization: input.package.defaultLocalization?.tag,
defaultSwiftSettings: try input.package.defaultSwiftSettings?.map { try .init($0) },
defaultCSettings: try input.package.defaultCSettings?.map { try .init($0) },
defaultCXXSettings: try input.package.defaultCXXSettings?.map { try .init($0) },
defaultLinkerSettings: try input.package.defaultLinkerSettings?.map { try .init($0) },
platforms: try input.package.platforms.map { try Self.parsePlatforms($0) } ?? [],
targets: try input.package.targets.map { try Self.parseTarget(target: $0, identityResolver: identityResolver) },
pkgConfig: input.package.pkgConfig,
Expand Down Expand Up @@ -197,6 +205,13 @@ enum ManifestJSONParser {

let pluginUsages = target.pluginUsages?.map { TargetDescription.PluginUsage.init($0) }

let explictSettings = TargetDescription.ExplicitSettings(
swift: target.swiftSettings != nil,
c: target.cSettings != nil,
cxx: target.cxxSettings != nil,
linker: target.linkerSettings != nil
)

return try TargetDescription(
name: target.name,
dependencies: dependencies,
Expand All @@ -212,6 +227,7 @@ enum ManifestJSONParser {
providers: providers,
pluginCapability: pluginCapability,
settings: try Self.parseBuildSettings(target),
explicitSettings: explictSettings,
checksum: target.checksum,
pluginUsages: pluginUsages
)
Expand Down Expand Up @@ -795,6 +811,8 @@ extension TargetBuildSettingDescription.Kind {
}

return .defaultIsolation(isolation)
case "inherited":
return .inherited
default:
throw InternalError("invalid build setting \(name)")
}
Expand Down
9 changes: 9 additions & 0 deletions Sources/PackageLoading/ManifestLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -361,13 +361,22 @@ public final class ManifestLoader: ManifestLoaderProtocol {
))
}

let defaultSwiftSettings = parsedManifest.defaultSwiftSettings ?? []
let defaultCSettings = parsedManifest.defaultCSettings ?? []
let defaultCXXSettings = parsedManifest.defaultCXXSettings ?? []
let defaultLinkerSettings = parsedManifest.defaultLinkerSettings ?? []

let defaultSettings: [TargetBuildSettingDescription.Setting] =
defaultSwiftSettings + defaultCSettings + defaultCXXSettings + defaultLinkerSettings

let manifest = Manifest(
displayName: parsedManifest.name,
packageIdentity: packageIdentity,
path: manifestPath,
packageKind: packageKind,
packageLocation: packageLocation,
defaultLocalization: parsedManifest.defaultLocalization,
defaultSettings: defaultSettings,
platforms: parsedManifest.platforms,
version: packageVersion?.version,
revision: packageVersion?.revision,
Expand Down
53 changes: 52 additions & 1 deletion Sources/PackageLoading/PackageBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,55 @@ public final class PackageBuilder {
}
}

private func resolvedSettings(for target: TargetDescription) throws -> [TargetBuildSettingDescription.Setting] {
var resolved: [TargetBuildSettingDescription.Setting] = []

// first, validate defaults
for setting in manifest.defaultSettings ?? [] {
if case .unsafeFlags = setting.kind {
throw ModuleError.invalidManifestConfig(
self.identity.description, "default settings cannot contain unsafe flags"
)
}
}

// copy over all existing settings, substituting in defaults if inherited is encountered.
for setting in target.settings {
switch setting.kind {
case .inherited:
if setting.condition != nil {
throw ModuleError.invalidManifestConfig(
self.identity.description, "inherited settings cannot use conditions"
)
}

let defaults = manifest.defaultSettings?.filter({ $0.tool == setting.tool }) ?? []
resolved.append(contentsOf: defaults)
default:
resolved.append(setting)
}
}

// Now, apply the defaults if nothing explicit is present.
if !target.explicitSettings.swift {
resolved.append(contentsOf: manifest.defaultSettings?.filter({ $0.tool == .swift }) ?? [])
}

if !target.explicitSettings.c {
resolved.append(contentsOf: manifest.defaultSettings?.filter({ $0.tool == .c }) ?? [])
}

if !target.explicitSettings.cxx {
resolved.append(contentsOf: manifest.defaultSettings?.filter({ $0.tool == .cxx }) ?? [])
}

if !target.explicitSettings.linker {
resolved.append(contentsOf: manifest.defaultSettings?.filter({ $0.tool == .linker }) ?? [])
}

return resolved
}

/// Creates build setting assignment table for the given target.
func buildSettings(
for target: TargetDescription?,
Expand All @@ -1103,7 +1152,7 @@ public final class PackageBuilder {
table.add(versionAssignment, for: .SWIFT_VERSION)

// Process each setting.
for setting in target.settings {
for setting in try resolvedSettings(for: target) {
if let traits = setting.condition?.traits, traits.intersection(self.enabledTraits.names).isEmpty {
// The setting is currently not enabled so we should skip it
continue
Expand Down Expand Up @@ -1341,6 +1390,8 @@ public final class PackageBuilder {
}

values = ["-default-isolation", isolation.rawValue]
case .inherited:
throw InternalError("inherited cannot be in resolved setttings")
}

// Create an assignment for this setting.
Expand Down
8 changes: 7 additions & 1 deletion Sources/PackageModel/Manifest/Manifest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public final class Manifest: Sendable {
/// The declared package dependencies.
public let dependencies: [PackageDependency]

/// The defaults to be used when resolving settings for all targets.
public let defaultSettings: [TargetBuildSettingDescription.Setting]?

/// The targets declared in the manifest.
public let targets: [TargetDescription]

Expand Down Expand Up @@ -116,6 +119,7 @@ public final class Manifest: Sendable {
packageKind: PackageReference.Kind,
packageLocation: String,
defaultLocalization: String?,
defaultSettings: [TargetBuildSettingDescription.Setting] = [],
platforms: [PlatformDescription],
version: TSCUtility.Version?,
revision: String?,
Expand All @@ -137,6 +141,7 @@ public final class Manifest: Sendable {
self.packageKind = packageKind
self.packageLocation = packageLocation
self.defaultLocalization = defaultLocalization
self.defaultSettings = defaultSettings
self.platforms = platforms
self.version = version
self.revision = revision
Expand Down Expand Up @@ -633,7 +638,7 @@ extension Manifest: Encodable {
case name, path, url, version, targetMap, toolsVersion,
pkgConfig, providers, cLanguageStandard, cxxLanguageStandard, swiftLanguageVersions,
dependencies, products, targets, traits, platforms, packageKind, revision,
defaultLocalization
defaultLocalization, defaultSettings
}

/// Coding user info key for dump-package command.
Expand All @@ -656,6 +661,7 @@ extension Manifest: Encodable {

try container.encode(self.toolsVersion, forKey: .toolsVersion)
try container.encode(self.defaultLocalization, forKey: .defaultLocalization)
try container.encode(self.defaultSettings, forKey: .defaultSettings)
try container.encode(self.pkgConfig, forKey: .pkgConfig)
try container.encode(self.providers, forKey: .providers)
try container.encode(self.cLanguageStandard, forKey: .cLanguageStandard)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,16 @@ public enum TargetBuildSettingDescription {

case defaultIsolation(DefaultIsolation)

case inherited

public var isUnsafeFlags: Bool {
switch self {
case .unsafeFlags(let flags):
// If `.unsafeFlags` is used, but doesn't specify any flags, we treat it the same way as not specifying it.
return !flags.isEmpty
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode,
.enableUpcomingFeature, .enableExperimentalFeature, .strictMemorySafety, .swiftLanguageMode,
.treatAllWarnings, .treatWarning, .enableWarning, .disableWarning, .defaultIsolation:
.treatAllWarnings, .treatWarning, .enableWarning, .disableWarning, .defaultIsolation, .inherited:
return false
}
}
Expand All @@ -93,5 +95,34 @@ public enum TargetBuildSettingDescription {
self.kind = kind
self.condition = condition
}

public func overridesDefault(_ defaultSetting: Setting) -> Bool {
guard tool == defaultSetting.tool else {
return false
}

switch (kind, defaultSetting.kind) {
case (.defaultIsolation, .defaultIsolation):
return true
case (.interoperabilityMode, .interoperabilityMode):
return true
case (.swiftLanguageMode, .swiftLanguageMode):
return true
case (.treatAllWarnings, .treatAllWarnings):
return true
case (.unsafeFlags, .unsafeFlags):
return true
case (.define, .define):
return true
case (.treatWarning(let value, _), .treatWarning(let defaultValue, _)):
return value == defaultValue
case (.enableWarning(let value), .disableWarning(let defaultValue)):
return value == defaultValue
case (.disableWarning(let value), .enableWarning(let defaultValue)):
return value == defaultValue
default:
return false
}
}
}
}
58 changes: 58 additions & 0 deletions Sources/PackageModel/Manifest/TargetDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,25 @@ public struct TargetDescription: Hashable, Encodable, Sendable {
/// The target-specific build settings declared in this target.
public let settings: [TargetBuildSettingDescription.Setting]

/// Models the presence of explicitly-defined settings for each tool type.
public let explicitSettings: ExplicitSettings

public struct ExplicitSettings: Hashable, Codable, Sendable {
public var swift: Bool
public var c: Bool
public var cxx: Bool
public var linker: Bool

public init(swift: Bool, c: Bool, cxx: Bool, linker: Bool) {
self.swift = swift
self.c = c
self.cxx = cxx
self.linker = linker
}

public static let all = Self.init(swift: true, c: true, cxx: true, linker: true)
public static let none = Self.init(swift: false, c: false, cxx: false, linker: false)
}
/// The binary target checksum.
public let checksum: String?

Expand All @@ -209,6 +228,7 @@ public struct TargetDescription: Hashable, Encodable, Sendable {
providers: [SystemPackageProviderDescription]? = nil,
pluginCapability: PluginCapability? = nil,
settings: [TargetBuildSettingDescription.Setting] = [],
explicitSettings: ExplicitSettings = .all,
checksum: String? = nil,
pluginUsages: [PluginUsage]? = nil
) throws {
Expand Down Expand Up @@ -445,6 +465,43 @@ public struct TargetDescription: Hashable, Encodable, Sendable {
) }
}

// ensure that settings and settings presense detection are consistent
if settings.filter({ $0.tool == .swift}).isEmpty == false && explicitSettings.swift == false {
throw Error.disallowedPropertyInTarget(
targetName: name,
targetType: targetType,
propertyName: "explicitSettings",
value: "swift"
)
}

if settings.filter({ $0.tool == .c}).isEmpty == false && explicitSettings.c == false {
throw Error.disallowedPropertyInTarget(
targetName: name,
targetType: targetType,
propertyName: "explicitSettings",
value: "c"
)
}

if settings.filter({ $0.tool == .cxx}).isEmpty == false && explicitSettings.cxx == false {
throw Error.disallowedPropertyInTarget(
targetName: name,
targetType: targetType,
propertyName: "explicitSettings",
value: "cxx"
)
}

if settings.filter({ $0.tool == .linker}).isEmpty == false && explicitSettings.linker == false {
throw Error.disallowedPropertyInTarget(
targetName: name,
targetType: targetType,
propertyName: "explicitSettings",
value: "linker"
)
}

self.name = name
self.dependencies = dependencies
self.path = path
Expand All @@ -461,6 +518,7 @@ public struct TargetDescription: Hashable, Encodable, Sendable {
self.settings = settings
self.checksum = checksum
self.pluginUsages = pluginUsages
self.explicitSettings = explicitSettings
}
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/PackageModel/ManifestSourceGeneration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,8 @@ fileprivate extension SourceCodeFragment {
params.append(SourceCodeFragment(from: condition))
}
self.init(enum: setting.kind.name, subnodes: params)
case .inherited:
self.init(enum: setting.kind.name, subnodes: [])
}
}

Expand Down Expand Up @@ -1215,6 +1217,8 @@ extension TargetBuildSettingDescription.Kind {
return "disableWarning"
case .defaultIsolation:
return "defaultIsolation"
case .inherited:
return "inherited"
}
}
}
Expand Down
Loading
Loading