Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@
"preLaunchTask": "swift: Build Release vips-tool",
"target": "vips-tool",
"configuration": "release"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:swift-vips-generator}",
"name": "Debug vips-generator",
"target": "vips-generator",
"configuration": "debug",
"preLaunchTask": "swift: Build Debug vips-generator"
},
{
"type": "swift",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:swift-vips-generator}",
"name": "Release vips-generator",
"target": "vips-generator",
"configuration": "release",
"preLaunchTask": "swift: Build Release vips-generator"
}
]
}
28 changes: 18 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ swift run vips-tool
# Build for release
swift build -c release

# Generate Swift wrappers from libvips operations (requires PyVIPS)
python3 tools/generate-swift-wrappers.py
# Run the code generator manually (optional - runs automatically during build)
swift run vips-generator --verbose
```

Depending on the environment, swift might be installed in `$HOME/.local/share/swiftly/bin/swift`.
Expand Down Expand Up @@ -72,13 +72,18 @@ The Swift wrapper mirrors libvips' modular structure in `Sources/VIPS/`:
- **Conversion/**: Image format and geometric conversions (resize, rotate, flip, etc.)
- **Create/**: Image creation and generation functions
- **Draw/**: Drawing operations and compositing
- **Generated/**: Auto-generated Swift wrappers for libvips operations (updated via `tools/generate-swift-wrappers.py`)
- **Convolution/**: Convolution and filtering operations
- **Histogram/**: Histogram analysis operations
- **Morphology/**: Morphological image processing operations
- **Resample/**: Image resampling and interpolation
- **CvipsShim/**: C interop layer for functionality not directly accessible from Swift (uses C macros and GObject methods)

Additional modules outside `Sources/VIPS/`:

- **VIPSIntrospection/**: Swift library for introspecting libvips operations at runtime
- **VIPSGenerator/**: Swift-based code generator that produces Swift wrappers from libvips introspection
- **Plugins/VIPSGeneratorPlugin/**: SwiftPM build tool plugin that runs the generator during build

## Key Development Patterns

- Uses swift naming conventions for method names and types. But follows closely the libvips names for easier searchability.
Expand All @@ -96,14 +101,16 @@ The Swift wrapper mirrors libvips' modular structure in `Sources/VIPS/`:

## Code Generation

The project uses automated code generation to create Swift wrappers:
The project uses a pure Swift code generator integrated as a SwiftPM build plugin:

- **Generator**: `tools/generate-swift-wrappers.py` uses PyVIPS to introspect libvips operations
- **Generated Files**: Located in `Sources/VIPS/Generated/` directory, organized by operation category
- **Requirements**: Requires `pip install pyvips` to run the generator
- **Usage**: Run `python3 tools/generate-swift-wrappers.py` to regenerate wrappers
- **Build Plugin**: `VIPSGeneratorPlugin` automatically runs during `swift build` when outputs are missing
- **Generator**: `vips-generator` executable uses `VIPSIntrospection` to query libvips operations at runtime
- **Generated Files**: Created in the plugin work directory during build, organized by operation category
- **Manual Usage**: Run `swift run vips-generator --verbose` to regenerate (or `--dry-run` to preview)
- **Convention**: Generated code follows Swift naming conventions while preserving libvips operation names for searchability

The generator introspects libvips directly at build time, ensuring wrappers match the installed libvips version.

## Testing Framework

- **Framework**: Uses Swift Testing (not XCTest) for modern Swift testing infrastructure
Expand All @@ -129,6 +136,7 @@ Check `docs/operations_todo.md` for current implementation roadmap. Arithmetic o

## Swift 6 Compatibility

- **Tools Version**: Requires Swift 6.2+ (`swift-tools-version:6.2`)
- **Language Mode**: Built with Swift 6 language mode enabled (`swiftLanguageModes: [.v6]`)
- **Concurrency**: Ready for strict concurrency checking
- **Dependencies**: Uses swift-log for logging functionality
- **Concurrency**: Ready for strict concurrency checking with experimental features enabled
- **Dependencies**: Uses swift-log for logging and swift-subprocess for process execution
20 changes: 19 additions & 1 deletion Package.resolved

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

27 changes: 26 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ let package = Package(
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(name: "VIPS", targets: ["VIPS"]),
.library(name: "VIPSIntrospection", targets: ["VIPSIntrospection"]),
.executable(name: "vips-generator", targets: ["vips-generator"]),
],
traits: [
"FoundationSupport",
],
dependencies: [
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0")
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
.package(url: "https://github.com/swiftlang/swift-subprocess.git", from: "0.2.1"),
],
targets: [
.systemLibrary(name: "Cvips",
Expand All @@ -24,6 +27,12 @@ let package = Package(
dependencies: [
"Cvips"
]),
// Build tool plugin that generates Swift wrappers from libvips introspection
.plugin(
name: "VIPSGeneratorPlugin",
capability: .buildTool(),
dependencies: ["vips-generator"]
),
.target(
name: "VIPS",
dependencies: [
Expand All @@ -35,7 +44,23 @@ let package = Package(
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("InferIsolatedConformances"),
.enableExperimentalFeature("Lifetimes")
],
plugins: [
.plugin(name: "VIPSGeneratorPlugin")
]),
.target(
name: "VIPSIntrospection",
dependencies: [
"Cvips",
"CvipsShim"
]),
.executableTarget(name: "vips-generator",
dependencies: [
"VIPSIntrospection",
.product(name: "Subprocess", package: "swift-subprocess")
],
path: "Sources/VIPSGenerator"
),
.executableTarget(name: "vips-tool",
dependencies: ["VIPS", "Cvips"]
),
Expand Down
11 changes: 0 additions & 11 deletions Pipfile

This file was deleted.

84 changes: 84 additions & 0 deletions Plugins/VIPSGeneratorPlugin/plugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// plugin.swift
// VIPSGeneratorPlugin
//
// Build tool plugin that generates Swift wrappers for libvips operations
//

import Foundation
import PackagePlugin

@main
struct VIPSGeneratorPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
// Only apply to the VIPS target
guard target.name == "VIPS" else {
return []
}

// Get the generator tool
let generator = try context.tool(named: "vips-generator")

// Output directory for generated files (using URL-based API)
let outputDir = context.pluginWorkDirectoryURL.appending(path: "Generated")

// Discover output files by running the generator with --list-outputs
let outputFiles = try discoverOutputFiles(
generator: generator.url,
outputDir: outputDir
)

// Use a build command - runs when outputs are missing or inputs changed
// Since there are no file inputs (we introspect libvips at runtime),
// this will run when outputs are missing (first build after clone)
return [
.buildCommand(
displayName: "Generating VIPS Swift wrappers",
executable: generator.url,
arguments: [
"--output-dir", outputDir.path(),
"--verbose"
],
inputFiles: [],
outputFiles: outputFiles
)
]
}

/// Discover output files by running the generator with --list-outputs
private func discoverOutputFiles(generator: URL, outputDir: URL) throws -> [URL] {
let process = Process()
process.executableURL = generator
process.arguments = ["--list-outputs", "--output-dir", outputDir.path()]

let pipe = Pipe()
process.standardOutput = pipe
process.standardError = Pipe() // Suppress stderr

try process.run()
process.waitUntilExit()

guard process.terminationStatus == 0 else {
throw PluginError.generatorFailed(status: process.terminationStatus)
}

let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output = String(data: data, encoding: .utf8) else {
throw PluginError.invalidGeneratorOutput
}

// Parse output - one file path per line
let filePaths = output
.split(separator: "\n")
.map { String($0).trimmingCharacters(in: .whitespaces) }
.filter { !$0.isEmpty }
.map { URL(fileURLWithPath: $0) }

return filePaths
}
}

enum PluginError: Error {
case generatorFailed(status: Int32)
case invalidGeneratorOutput
}
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ swift build -c release

## Requirements

- Swift 6.0+
- Swift 6.2+
- libvips 8.12+ installed on system
- Linux or macOS

Expand All @@ -299,12 +299,13 @@ swift build -c release
SwiftVIPS mirrors libvips' modular structure:

- **`Core/`**: Initialization, memory management, fundamental types
- **`Arithmetic/`**: Mathematical operations and operator overloading
- **`Arithmetic/`**: Mathematical operations and operator overloading
- **`Conversion/`**: Image transformations and geometric operations
- **`Foreign/`**: File format support (organized by format)
- **`Generated/`**: Auto-generated bindings for libvips operations
- **`CvipsShim/`**: C interoperability layer

Code generation uses a SwiftPM build plugin that automatically generates Swift wrappers from libvips introspection at build time.

## Performance

SwiftVIPS inherits libvips' performance characteristics:
Expand Down
Loading