Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,32 @@ jobs:
windows_6_2_enabled: true
windows_nightly_next_enabled: true
windows_nightly_main_enabled: true

construct-linkage-test-matrix:
name: Construct linkage matrix
runs-on: ubuntu-latest
outputs:
linkage-test-matrix: '${{ steps.generate-matrix.outputs.linkage-test-matrix }}'
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false
- id: generate-matrix
run: echo "linkage-test-matrix=$(curl -s https://raw.githubusercontent.com/apple/swift-nio/main/scripts/generate_matrix.sh | bash)" >> "$GITHUB_OUTPUT"
env:
MATRIX_LINUX_SETUP_COMMAND: apt-get update -y && apt-get install -yq jq && git config --global --add safe.directory /swift-openapi-generator
MATRIX_LINUX_COMMAND: ./scripts/run-linkage-test.sh
MATRIX_LINUX_5_10_ENABLED: false
MATRIX_LINUX_6_0_ENABLED: false
MATRIX_LINUX_6_1_ENABLED: false
MATRIX_LINUX_NIGHTLY_NEXT_ENABLED: false
MATRIX_LINUX_NIGHTLY_MAIN_ENABLED: false

linkage-test:
name: Linkage test
needs: construct-linkage-test-matrix
uses: apple/swift-nio/.github/workflows/swift_test_matrix.yml@main
with:
name: "Linkage test"
matrix_string: '${{ needs.construct-linkage-test-matrix.outputs.linkage-test-matrix }}'
29 changes: 29 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,32 @@ jobs:
windows_6_2_enabled: true
windows_nightly_next_enabled: true
windows_nightly_main_enabled: true

construct-linkage-test-matrix:
name: Construct linkage matrix
runs-on: ubuntu-latest
outputs:
linkage-test-matrix: '${{ steps.generate-matrix.outputs.linkage-test-matrix }}'
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false
- id: generate-matrix
run: echo "linkage-test-matrix=$(curl -s https://raw.githubusercontent.com/apple/swift-nio/main/scripts/generate_matrix.sh | bash)" >> "$GITHUB_OUTPUT"
env:
MATRIX_LINUX_SETUP_COMMAND: apt-get update -y && apt-get install -yq jq && git config --global --add safe.directory /swift-openapi-generator
MATRIX_LINUX_COMMAND: ./scripts/run-linkage-test.sh
MATRIX_LINUX_5_10_ENABLED: false
MATRIX_LINUX_6_0_ENABLED: false
MATRIX_LINUX_6_1_ENABLED: false
MATRIX_LINUX_NIGHTLY_NEXT_ENABLED: false
MATRIX_LINUX_NIGHTLY_MAIN_ENABLED: false

linkage-test:
name: Linkage test
needs: construct-linkage-test-matrix
uses: apple/swift-nio/.github/workflows/swift_test_matrix.yml@main
with:
name: "Linkage test"
matrix_string: '${{ needs.construct-linkage-test-matrix.outputs.linkage-test-matrix }}'
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.10
// swift-tools-version:6.1
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
Expand Down Expand Up @@ -54,7 +54,7 @@ let package = Package(
// Tests-only: Runtime library linked by generated code, and also
// helps keep the runtime library new enough to work with the generated
// code.
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.8.2"),
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.11.0", traits: []),
.package(url: "https://github.com/apple/swift-http-types", from: "1.0.2"),
],
targets: [
Expand Down
167 changes: 167 additions & 0 deletions Package@swift-5.10.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// swift-tools-version:5.10
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Foundation
import PackageDescription

// General Swift-settings for all targets.
var swiftSettings: [SwiftSetting] = [
// https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md
// Require `any` for existential types.
.enableUpcomingFeature("ExistentialAny"), .enableExperimentalFeature("StrictConcurrency=complete"),
]

let package = Package(
name: "swift-openapi-generator",
platforms: [
.macOS(.v10_15),

// The platforms below are not currently supported for running
// the generator itself. We include them here to allow the generator
// to emit a more descriptive compiler error.
.iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1),
],
products: [
.executable(name: "swift-openapi-generator", targets: ["swift-openapi-generator"]),
.plugin(name: "OpenAPIGenerator", targets: ["OpenAPIGenerator"]),
.plugin(name: "OpenAPIGeneratorCommand", targets: ["OpenAPIGeneratorCommand"]),
.library(name: "_OpenAPIGeneratorCore", targets: ["_OpenAPIGeneratorCore"]),
],
dependencies: [

// General algorithms
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
.package(url: "https://github.com/apple/swift-collections", from: "1.1.4"),

// Read OpenAPI documents
.package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "3.9.0"),
.package(url: "https://github.com/jpsim/Yams", "4.0.0"..<"7.0.0"),

// CLI Tool
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),

// Tests-only: Runtime library linked by generated code, and also
// helps keep the runtime library new enough to work with the generated
// code.
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.8.2"),
.package(url: "https://github.com/apple/swift-http-types", from: "1.0.2"),
],
targets: [

// Generator Core
.target(
name: "_OpenAPIGeneratorCore",
dependencies: [
.product(name: "OpenAPIKit", package: "OpenAPIKit"),
.product(name: "OpenAPIKit30", package: "OpenAPIKit"),
.product(name: "OpenAPIKitCompat", package: "OpenAPIKit"),
.product(name: "Algorithms", package: "swift-algorithms"),
.product(name: "OrderedCollections", package: "swift-collections"),
.product(name: "Yams", package: "Yams"),
],
swiftSettings: swiftSettings
),

// Generator Core Tests
.testTarget(
name: "OpenAPIGeneratorCoreTests",
dependencies: ["_OpenAPIGeneratorCore"],
swiftSettings: swiftSettings
),

// GeneratorReferenceTests
.testTarget(
name: "OpenAPIGeneratorReferenceTests",
dependencies: ["_OpenAPIGeneratorCore"],
resources: [.copy("Resources")],
swiftSettings: swiftSettings
),

// Common types for concrete PetstoreConsumer*Tests test targets.
.target(
name: "PetstoreConsumerTestCore",
dependencies: [
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "HTTPTypes", package: "swift-http-types"),
],
swiftSettings: swiftSettings
),

// PetstoreConsumerTests
// Builds and tests the reference code from GeneratorReferenceTests
// to ensure it actually works correctly at runtime.
.testTarget(
name: "PetstoreConsumerTests",
dependencies: ["PetstoreConsumerTestCore"],
swiftSettings: swiftSettings
),

// Test Target for swift-openapi-generator
.testTarget(
name: "OpenAPIGeneratorTests",
dependencies: [
"_OpenAPIGeneratorCore",
// Everything except windows: https://github.com/swiftlang/swift-package-manager/issues/6367
.target(
name: "swift-openapi-generator",
condition: .when(platforms: [.android, .linux, .macOS, .openbsd, .wasi, .custom("freebsd")])
), .product(name: "ArgumentParser", package: "swift-argument-parser"),
],
resources: [.copy("Resources")],
swiftSettings: swiftSettings
),

// Generator CLI
.executableTarget(
name: "swift-openapi-generator",
dependencies: [
"_OpenAPIGeneratorCore", .product(name: "ArgumentParser", package: "swift-argument-parser"),
],
swiftSettings: swiftSettings
),

// Build Plugin
.plugin(name: "OpenAPIGenerator", capability: .buildTool(), dependencies: ["swift-openapi-generator"]),

// Command Plugin
.plugin(
name: "OpenAPIGeneratorCommand",
capability: .command(
intent: .custom(
verb: "generate-code-from-openapi",
description: "Generate Swift code from an OpenAPI document."
),
permissions: [
.writeToPackageDirectory(
reason: "To write the generated Swift files back into the source directory of the package."
)
]
),
dependencies: ["swift-openapi-generator"]
),
]
)

// --- STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- //
for target in package.targets {
switch target.type {
case .regular, .test, .executable:
var settings = target.swiftSettings ?? []
// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md
settings.append(.enableUpcomingFeature("MemberImportVisibility"))
target.swiftSettings = settings
case .macro, .plugin, .system, .binary: () // not applicable
@unknown default: () // we don't know what to do here, do nothing
}
}// --- END: STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- //
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@
//
//===----------------------------------------------------------------------===//

/// A top-level import statement, that may be conditional based on a compile-time condition
enum ImportStatement: Equatable, Codable {
/// Imports based on compile time condition
case conditional(
condition: Condition,
thenImportDescription: ImportDescription,
elseImportDescription: ImportDescription
)

/// Always imported
case always(ImportDescription)

enum Condition: Equatable, Codable {
/// A condition on whether we can import a specific module
case canImport(String)
}
}

/// A description of an import declaration.
///
/// For example: `import Foo`.
Expand Down Expand Up @@ -678,6 +696,9 @@ indirect enum Declaration: Equatable, Codable {

/// An enum case declaration.
case enumCase(EnumCaseDescription)

/// A #if canImport declaration
case canImportConditional(String, then: [Declaration], else: [Declaration])
}

/// A description of a deprecation notice.
Expand Down Expand Up @@ -1052,7 +1073,7 @@ struct FileDescription: Equatable, Codable {

/// Import statements placed below the top comment, but before the code
/// blocks.
var imports: [ImportDescription]?
var imports: [ImportStatement]?

/// The code blocks that represent the main contents of the file.
var codeBlocks: [CodeBlock]
Expand Down Expand Up @@ -1675,6 +1696,7 @@ extension Declaration {
case .protocol(let protocolDescription): return protocolDescription.accessModifier
case .function(let functionDescription): return functionDescription.signature.accessModifier
case .enumCase: return nil
case .canImportConditional: return nil
}
}
set {
Expand Down Expand Up @@ -1707,6 +1729,7 @@ extension Declaration {
functionDescription.signature.accessModifier = newValue
self = .function(functionDescription)
case .enumCase: break
case .canImportConditional: break
}
}
}
Expand Down
39 changes: 37 additions & 2 deletions Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,34 @@ struct TextBasedRenderer: RendererProtocol {
}

/// Renders the specified import statements.
func renderImports(_ imports: [ImportDescription]?) { (imports ?? []).forEach(renderImport) }
func renderImports(_ imports: [ImportStatement]?) { (imports ?? []).forEach(renderImport) }

/// Renders a single import statement.
func renderImport(_ description: ImportDescription) {
func renderImport(_ importStatement: ImportStatement) {
switch importStatement {
case .conditional(let condition, let thenImportDescription, let elseImportDescription):
writer.writeLine("#if \(renderImportCondition(condition))")
renderImport(thenImportDescription)
writer.writeLine("#else")
renderImport(elseImportDescription)
writer.writeLine("#endif")

case .always(let importDescription): self.renderImport(importDescription)
}
}

/// Renders a import condition
func renderImportCondition(_ condition: ImportStatement.Condition) -> String {
switch condition {
case .canImport(let argument): "canImport(\(argument))"
}
}

/// Renders the specified import statements.
private func renderImports(_ imports: [ImportDescription]?) { (imports ?? []).forEach(renderImport) }

/// Renders a single import statement.
private func renderImport(_ description: ImportDescription) {
func render(preconcurrency: Bool) {
let spiPrefix = description.spi.map { "@_spi(\($0)) " } ?? ""
let preconcurrencyPrefix = preconcurrency ? "@preconcurrency " : ""
Expand Down Expand Up @@ -713,6 +737,8 @@ struct TextBasedRenderer: RendererProtocol {
case .typealias(let typealiasDescription): renderTypealias(typealiasDescription)
case .function(let functionDescription): renderFunction(functionDescription)
case .enumCase(let enumCase): renderEnumCase(enumCase)
case .canImportConditional(let condition, let thenDecls, let elseDecls):
renderCanImportConditional(condition, thenDecls, elseDecls)
}
}

Expand Down Expand Up @@ -836,6 +862,15 @@ struct TextBasedRenderer: RendererProtocol {
writer.writeLine(line)
}

/// Renders a canImport with both then and else branches
func renderCanImportConditional(_ module: String, _ thenDecls: [Declaration], _ elseDecls: [Declaration]) {
writer.writeLine("#if canImport(\(module))")
for thenDecl in thenDecls { renderDeclaration(thenDecl) }
writer.writeLine("#else")
for elseDecl in elseDecls { renderDeclaration(elseDecl) }
writer.writeLine("#endif")
}

/// Renders the specified code block item.
func renderCodeBlockItem(_ description: CodeBlockItem) {
switch description {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ struct ClientFileTranslator: FileTranslator {
let topComment = self.topComment

let imports =
Constants.File.clientServerImports + config.additionalImports.map { ImportDescription(moduleName: $0) }
Constants.File.clientServerImports
+ config.additionalImports.map { .always(ImportDescription(moduleName: $0)) }

let clientMethodDecls = try OperationDescription.all(from: doc.paths, in: components, context: context)
.map(translateClientMethod(_:))
Expand Down Expand Up @@ -67,7 +68,7 @@ struct ClientFileTranslator: FileTranslator {
accessModifier: config.access,
kind: .initializer,
parameters: [
.init(label: "serverURL", type: .init(TypeName.url)),
.init(label: "serverURL", type: .init(TypeName.foundationURLTypeAlias)),
.init(
label: "configuration",
type: .member(Constants.Configuration.typeName),
Expand Down
Loading