Skip to content
Closed
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
97 changes: 97 additions & 0 deletions .github/workflows/bindings-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,100 @@ jobs:
shell: pwsh
run: |
Write-Host "Windows Python native build, pure tests, typing, declaration audit, and archive inspection pass; initialized idalib execution remains disabled under the established nondiagnostic Windows headless boundary."

build-swift:
name: Swift Bindings - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-14]

steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
submodules: recursive

- name: Checkout IDA SDK
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ${{ env.IDA_SDK_REPO }}
ref: ${{ env.IDA_SDK_REF }}
path: ida-sdk
submodules: recursive

- name: Setup uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
version: "0.11.28"
github-token: ${{ github.token }}

- name: Install IDA Pro
env:
HCLI_API_KEY: ${{ secrets.HCLI_KEY }}
shell: bash
run: |
LICENSE_OUTPUT=$(uvx --from ida-hcli hcli --disable-updates license list)
LICENSE_ID=$(printf '%s\n' "$LICENSE_OUTPUT" | python3 scripts/select_hcli_license.py)
echo "::add-mask::$LICENSE_ID"
echo "Selected active named IDA product license"

if [[ "$(uname -m)" == "arm64" ]]; then
ASSET_KEY="release/9.4/ida-pro/ida-pro_94_armmac.app.zip"
else
ASSET_KEY="release/9.4/ida-pro/ida-pro_94_x64mac.app.zip"
fi

mkdir -p ./ida-installer
uvx --from ida-hcli hcli --disable-updates download --output-dir ./ida-installer "$ASSET_KEY"

INSTALLER_FILE=$(python3 -c "import glob; files=sorted(glob.glob('ida-installer/*')); print(files[0] if files else '')")
if [[ -z "$INSTALLER_FILE" ]]; then
echo "error: no installer file found in ./ida-installer"
ls -la ./ida-installer
exit 1
fi
echo "Using installer file: $INSTALLER_FILE"
uvx --from ida-hcli hcli --disable-updates ida install --set-default --accept-eula --yes --license-id "$LICENSE_ID" "$INSTALLER_FILE"

- name: Resolve IDASDK and IDADIR paths
shell: bash
run: |
SDK_ROOT="${GITHUB_WORKSPACE}/ida-sdk"
if [[ -f "${SDK_ROOT}/ida-cmake/bootstrap.cmake" ]]; then
echo "IDASDK=${SDK_ROOT}" >> "$GITHUB_ENV"
elif [[ -f "${SDK_ROOT}/src/cmake/bootstrap.cmake" || -f "${SDK_ROOT}/src/cmake/idasdkConfig.cmake" ]]; then
echo "IDASDK=${SDK_ROOT}/src" >> "$GITHUB_ENV"
elif [[ -f "${SDK_ROOT}/src/ida-cmake/bootstrap.cmake" ]]; then
echo "IDASDK=${SDK_ROOT}/src" >> "$GITHUB_ENV"
elif [[ -f "${SDK_ROOT}/cmake/bootstrap.cmake" || -f "${SDK_ROOT}/cmake/idasdkConfig.cmake" ]]; then
echo "IDASDK=${SDK_ROOT}" >> "$GITHUB_ENV"
else
echo "error: unable to locate SDK CMake entry point in SDK checkout"
exit 1
fi

IDADIR=$(python3 -c "import json, os; p = os.path.expanduser('~/.idapro/ida-config.json'); print(json.loads(open(p).read())['Paths']['ida-install-dir']) if os.path.exists(p) else print('')")
if [[ -f "$IDADIR/Contents/MacOS/libida.dylib" ]]; then
IDADIR="$IDADIR/Contents/MacOS"
fi
echo "IDADIR=$IDADIR" >> "$GITHUB_ENV"
echo "DYLD_LIBRARY_PATH=$IDADIR:$DYLD_LIBRARY_PATH" >> "$GITHUB_ENV"
echo "DYLD_FALLBACK_LIBRARY_PATH=$IDADIR:$DYLD_FALLBACK_LIBRARY_PATH" >> "$GITHUB_ENV"

- name: Report toolchain
shell: bash
run: swift --version

- name: Build native libraries
shell: bash
run: bindings/swift/scripts/build-libs.sh --arch "$(uname -m)" --build-type "${BUILD_TYPE}"

- name: Build Swift package
shell: bash
run: swift build

- name: Run Swift unit tests
shell: bash
run: swift test
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ __pycache__/
src/bin
obj/
build/
build*/
# Anchored: unanchored `build*/` also matches nested directories, and on a
# case-insensitive filesystem it swallows source directories such as
# bindings/swift/Plugins/BuildXCFramework/. `build/` above still covers
# nested build directories at any depth.
/build*/

# Workflow-owned SDK/runtime acquisition roots
ida-installer/
Expand Down
108 changes: 108 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// swift-tools-version: 6.2
import PackageDescription
import Foundation

// CIDAX links the static libraries produced by bindings/swift/scripts/build-libs.sh.
// Run that script once before `swift build`; it writes libidax.a and libidax_shim.a
// into bindings/swift/.build-libs (override with IDAX_LIB_DIR).
//
// To ship the C shim to downstream Xcode projects, `swift package build-xcframework`
// packages the same libraries as CIDAX.xcframework.
let libraryDirectory: String = {
if let directory = ProcessInfo.processInfo.environment["IDAX_LIB_DIR"] {
return directory
}
let packageDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent().path
return "\(packageDirectory)/bindings/swift/.build-libs"
}()

// libidax.a calls into the IDA runtime, so anything that links — the example
// executable and the test bundle — needs libida/libidalib on the link line.
// Mirrors the discovery order in bindings/rust/idax-sys/build.rs: $IDADIR
// first, then the installed applications.
let idaRuntimeDirectory: String? = {
let fileManager = FileManager.default
func containsRuntime(_ directory: String) -> Bool {
fileManager.fileExists(atPath: "\(directory)/libida.dylib")
}

if let directory = ProcessInfo.processInfo.environment["IDADIR"], containsRuntime(directory) {
return directory
}

let applications = (try? fileManager.contentsOfDirectory(atPath: "/Applications")) ?? []
return applications
.filter { $0.hasPrefix("IDA") && $0.hasSuffix(".app") }
.sorted()
.reversed()
.map { "/Applications/\($0)/Contents/MacOS" }
.first(where: containsRuntime)
}()

let idaRuntimeLinkerFlags: [String] = idaRuntimeDirectory.map { directory in
["-L\(directory)", "-lida", "-lidalib", "-Xlinker", "-rpath", "-Xlinker", directory]
} ?? []

let package = Package(
name: "IDAX",
platforms: [.macOS(.v13)],
products: [
.library(name: "IDAX", targets: ["IDAX"]),
],
targets: [
.target(
name: "CIDAX",
path: "bindings/swift/Sources/CIDAX",
publicHeadersPath: "include",
cSettings: [
.headerSearchPath("include"),
],
linkerSettings: [
.unsafeFlags([
"-L\(libraryDirectory)",
"-lidax", "-lidax_shim",
// libidax.a is C++, but SPM links CIDAX as a C target and so
// does not pull in the C++ runtime on its own.
"-lc++",
] + idaRuntimeLinkerFlags),
]
),
.target(
name: "IDAX",
dependencies: ["CIDAX"],
path: "bindings/swift/Sources/IDAX",
swiftSettings: [
.enableExperimentalFeature("SafeInteropWrappers"),
]
),
.executableTarget(
name: "idax-example",
dependencies: ["IDAX"],
path: "bindings/swift/Examples"
),
.testTarget(
name: "IDAXTests",
dependencies: ["IDAX"],
path: "bindings/swift/Tests/IDAXTests"
),
.plugin(
name: "BuildXCFramework",
capability: .command(
intent: .custom(
verb: "build-xcframework",
description: "Build CIDAX.xcframework (macOS arm64 + x86_64) from the C++ sources."
),
permissions: [
.writeToPackageDirectory(
reason: "Write the generated CIDAX.xcframework and CMake build artifacts into the package directory."
),
.allowNetworkConnections(
scope: .all(),
reason: "CMake FetchContent may download the IDA SDK when IDASDK is unset."
),
]
),
path: "bindings/swift/Plugins/BuildXCFramework"
),
]
)
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,22 @@ cpack --config build/CPackConfig.cmake -B build
# Produces idax-0.1.0-Darwin.tar.gz (or equivalent for your platform)
```

### Swift

The Swift package (macOS 13+) links static libraries rather than building them
through SPM, so build those once first:

```bash
bindings/swift/scripts/build-libs.sh # writes bindings/swift/.build-libs/
swift build
swift test
```

`IDADIR` is picked up if set; otherwise the manifest looks for an installed
IDA under `/Applications`. To hand the C shim to an Xcode project, run
`swift package build-xcframework`, which packages the same libraries as
`CIDAX.xcframework`.

---

## Testing strategy
Expand Down
Loading
Loading