Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9326175
create helper macros and basic version of OptionGroupPassthrough Memb…
Mcrich23 Oct 4, 2025
4f44ccd
chore(license): update licenses before push
Mcrich23 Oct 4, 2025
f4a71b8
chore(fmt): auto-format before push
Mcrich23 Oct 4, 2025
388015b
Delete PublicStructs.swift
Mcrich23 Oct 4, 2025
4f15015
applied OptionGroupPassthrough to all flag structs
Mcrich23 Oct 4, 2025
aaf1159
add DocC Documentation
Mcrich23 Oct 4, 2025
16cfd15
code cleanup and add comments
Mcrich23 Oct 4, 2025
6ef6557
Merge branch 'main' into add-command-option-group-function-macro
Mcrich23 Oct 13, 2025
f30621b
Merge branch 'main' into add-command-option-group-function-macro
Mcrich23 Oct 19, 2025
9643769
Merge branch 'apple:main' into add-command-option-group-function-macro
Mcrich23 Oct 22, 2025
6888a16
Merge branch 'main' into add-command-option-group-function-macro
Mcrich23 Oct 31, 2025
b2c9b52
Merge branch 'main' into add-command-option-group-function-macro
Mcrich23 Nov 5, 2025
8c6bf68
Merge branch 'main' into add-command-option-group-function-macro
Mcrich23 Nov 20, 2025
d515350
Merge branch 'apple:main' into add-command-option-group-function-macro
Mcrich23 Dec 3, 2025
7787d78
Update Package.swift
Mcrich23 Dec 3, 2025
1559e6d
Squashed commit of the following:
Mcrich23 Jan 20, 2026
68cfbb8
Update Package.resolved
Mcrich23 Jan 20, 2026
dad66f5
chore(license): update licenses before push
Mcrich23 Jan 20, 2026
0099292
Squashed commit of the following:
Mcrich23 Jan 22, 2026
d67c318
chore(license): update licenses before push
Mcrich23 Jan 22, 2026
94ba456
Squashed commit of the following:
Mcrich23 Jan 22, 2026
02a97cf
Merge branch 'main' into add-command-option-group-function-macro
Mcrich23 Jan 22, 2026
65781a4
Update Package.swift
Mcrich23 Jan 24, 2026
27b0b2b
Update Package.resolved
Mcrich23 Jan 24, 2026
b56dd44
chore(license): update licenses before push
Mcrich23 Jan 24, 2026
d0a477b
chore(fmt): auto-format before push
Mcrich23 Jan 24, 2026
c7adfe7
dependency map fixes
Mcrich23 Jan 28, 2026
8b79ec5
Squashed commit of the following:
Mcrich23 Feb 4, 2026
92e77c8
add @OptionGroupPassthrough to new flag groups
Mcrich23 Feb 4, 2026
7dc90d1
Update Package.resolved
Mcrich23 Feb 4, 2026
1d4e030
chore(fmt): auto-format before push
Mcrich23 Feb 4, 2026
61c0378
Squashed commit of the following:
Mcrich23 Feb 18, 2026
c383528
chore(license): update licenses before push
Mcrich23 Feb 18, 2026
76bf69c
chore(fmt): auto-format before push
Mcrich23 Feb 18, 2026
c9d2676
Merge branch 'main' into add-command-option-group-function-macro
Mcrich23 Feb 18, 2026
24f4920
remove changes for unnecessary files
Mcrich23 Feb 18, 2026
fc859ee
Merge branch 'add-command-option-group-function-macro' of https://git…
Mcrich23 Feb 18, 2026
82d5bd4
remove changes for unnecessary files
Mcrich23 Feb 18, 2026
a3bf110
Merge branch 'main' into add-command-option-group-function-macro
Mcrich23 Mar 10, 2026
33a37da
Merge branch 'apple:main' into add-command-option-group-function-macro
Mcrich23 Mar 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
9 changes: 9 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

// The swift-tools-version declares the minimum version of Swift required to build this package.

import CompilerPluginSupport
import Foundation
import PackageDescription

Expand Down Expand Up @@ -59,6 +60,7 @@ let package = Package(
.package(url: "https://github.com/orlandos-nl/DNSClient.git", from: "2.4.1"),
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.20.1"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin.git", from: "1.1.0"),
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "602.0.0"),
],
targets: [
.executableTarget(
Expand Down Expand Up @@ -189,6 +191,7 @@ let package = Package(
"ContainerResource",
"ContainerXPC",
"TerminalProgress",
"HelperMacros",
],
path: "Sources/Services/ContainerAPIService/Client"
),
Expand Down Expand Up @@ -476,6 +479,17 @@ let package = Package(
.define("BUILDER_SHIM_VERSION", to: "\"\(builderShimVersion)\""),
],
),
.macro(
name: "HelperMacrosMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
]
),
.target(
name: "HelperMacros",
dependencies: ["HelperMacrosMacros"]
),
.target(
name: "CAuditToken",
dependencies: [],
Expand Down
28 changes: 28 additions & 0 deletions Sources/HelperMacros/HelperMacros.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//

//
// HelperMacros.swift
// container
//
// Created by Morris Richman on 10/3/25.
//

import Foundation

/// Creates a function in OptionGroups called `passThroughCommands` to return an array of strings to be appended and passed down for Plugin support.
@attached(member, names: named(passThroughCommands))
public macro OptionGroupPassthrough() = #externalMacro(module: "HelperMacrosMacros", type: "OptionGroupPassthrough")
47 changes: 47 additions & 0 deletions Sources/HelperMacrosMacros/HelperMacrosMacros.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//

//
// HelperMacrosMacros.swift
// container
//
// Created by Morris Richman on 10/3/25.
//

import SwiftCompilerPlugin
import SwiftDiagnostics
import SwiftSyntaxMacros

@main
struct HelperMacrosMacros: CompilerPlugin {
let providingMacros: [Macro.Type] = [
OptionGroupPassthrough.self
]
}

extension String: @retroactive Error {
}

enum MacroExpansionError: Error {
case unsupportedDeclaration

var localizedDescription: String {
switch self {
case .unsupportedDeclaration:
return "Unsupported declaration for macro expansion."
}
}
}
213 changes: 213 additions & 0 deletions Sources/HelperMacrosMacros/OptionGroupPassthrough.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//

//
// OptionGroupPassthrough.swift
// container
//
// Created by Morris Richman on 10/3/25.
//

import Foundation
import SwiftSyntax
import SwiftSyntaxMacros

/// Creates a function in OptionGroups called `passThroughCommands` to return an array of strings to be appended and passed down for Plugin support.
public struct OptionGroupPassthrough: MemberMacro {
public static func expansion(
of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let structDecl = declaration.as(StructDeclSyntax.self) else {
throw MacroExpansionError.unsupportedDeclaration
}
let members = structDecl.memberBlock.members.filter({ $0.decl.is(VariableDeclSyntax.self) })
var commands: [CommandOutline] = []

// Append comman outlines for each member
for member in members {
guard let decl = member.decl.as(VariableDeclSyntax.self) else {
continue
}
if let option = decl.attributes.first(where: { $0.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "Option" }),
let option = option.as(AttributeSyntax.self)
{
commands.append(try getOptionPropertyCommands(option, decl: decl))
} else if let option = decl.attributes.first(where: { $0.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "Flag" }),
let option = option.as(AttributeSyntax.self)
{
commands.append(try getFlagPropertyCommands(option, decl: decl))
}
}

// Create begining of function
var function = """
/// Autogenerated by ``OptionGroupPassthrough``. This function returns the ``OptionGroup`` as an array of commands that can be passed down to a ``ContainerCommands`` command.
public func passThroughCommands() -> [String] {
var commands: [String] = []

"""

// Append the code for each command
for command in commands {
function.append(command.code)
function.append("")
}

// Close function
function.append("return commands\n}")

return [.init(stringLiteral: function)]
}

private static func getFlagPropertyCommands(_ option: AttributeSyntax, decl: VariableDeclSyntax) throws -> CommandOutline {
let (optionType, customName) = try getOptionNameType(option)
guard let identifierBinding = decl.bindings.first(where: { $0.pattern.is(IdentifierPatternSyntax.self) }),
let parameter = identifierBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier
else {
throw "Could Not Determine Variable"
}

// Get string command tack
let optionName = customName ?? parameter.text
let nameCommand = optionType.tacks + optionName

return CommandOutline(type: .flag, flag: nameCommand, variable: parameter.text)
}

private static func getOptionPropertyCommands(_ option: AttributeSyntax, decl: VariableDeclSyntax) throws -> CommandOutline {
let (optionType, customName) = try getOptionNameType(option)
guard let identifierBinding = decl.bindings.first(where: { $0.pattern.is(IdentifierPatternSyntax.self) }),
let parameter = identifierBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier
else {
throw "Could Not Determine Variable"
}

// Get string command tack
let optionName = customName ?? parameter.text
let nameCommand = optionType.tacks + optionName

return CommandOutline(type: .option, flag: nameCommand, variable: parameter.text)
}

private static func getOptionNameType(_ option: AttributeSyntax) throws -> (OptionNameType, String?) {
guard let attribute = option.arguments?.as(LabeledExprListSyntax.self)?.first(where: { $0.label?.text == "name" }) else {
// Default to long if not described in PropertyWrapper
return (.long, nil)
}
let expression: MemberAccessExprSyntax = try _getOptionNameTypeExpressionFromExpression(attribute.expression)
guard let optionType = OptionNameType(baseName: expression.declName.baseName) else {
throw "Error Parsing Option Name"
}

// Get the name of the custom short/long if needed
var customString: String?
if [OptionNameType.customLong, .customShort].contains(optionType) {
if let arrayExpression = attribute.expression.as(ArrayExprSyntax.self),
let last = arrayExpression.elements.last
{
customString = try _getCustomOptionNameFromExpression(last.expression)
} else {
customString = try _getCustomOptionNameFromExpression(attribute.expression)
}
}

return (optionType, customString)
}

private static func _getOptionNameTypeExpressionFromExpression(_ expression: ExprSyntax) throws -> MemberAccessExprSyntax {
if let expr = expression.as(MemberAccessExprSyntax.self) {
return expr
} else if let function = expression.as(FunctionCallExprSyntax.self),
let expr = function.calledExpression.as(MemberAccessExprSyntax.self)
{
return expr
} else if let array = expression.as(ArrayExprSyntax.self),
let last = array.elements.last
{
return try _getOptionNameTypeExpressionFromExpression(last.expression)
} else {
throw "Error Parsing Option Name Expression: \(expression)"
}
}
private static func _getCustomOptionNameFromExpression(_ expression: ExprSyntax) throws -> String? {
let customNameArguments = expression.as(FunctionCallExprSyntax.self)?.arguments
guard let customNameArg = customNameArguments?.first,
let segment = customNameArg.expression.as(StringLiteralExprSyntax.self)?.segments.first
else {
throw "Error Parsing Custom Option Name"
}
return segment.as(StringSegmentSyntax.self)?.content.text
}

private enum OptionNameType: String {
case short, long, customLong, customShort

init?(baseName: TokenSyntax) {
guard let result = OptionNameType(baseName: baseName.text) else {
return nil
}

self = result
}

init?(baseName: String) {
switch baseName {
case "shortAndLong": self = .long
case "customLong": self = .customLong
case "long": self = .long
case "customShort": self = .customShort
case "short": self = .short
default: return nil
}
}

var tacks: String {
switch self {
case .short, .customShort:
"-"
case .long, .customLong:
"--"
}
}
}
}

private struct CommandOutline {
let type: `Type`
let flag: String
let variable: String

enum `Type` {
case flag, option
}

var code: String {
switch type {
case .flag:
"""
if \(variable) {
commands.append("\(flag)")
}
"""
case .option:
"""
if "\\(\(variable), default: "%absolute-nil%")" != "%absolute-nil%" {
commands.append(contentsOf: ["\(flag)", "\\(\(variable), default: "%absolute-nil%")"])
}
"""
}
}
}
Loading