Disclaimer: This tool was created using LLM-assisted tooling. Do not expect code readability or quality; there may be rough edges.
Xcode Query (xcq) uses a GraphQL-style query language for predictable, composable queries against your Xcode project. Results are JSON and shaped by your selection set.
- Recommended (tap):
brew tap alvarhansen/xcodequery- Stable:
brew install xcq - HEAD (latest main):
brew install --HEAD xcq
After install, verify: xcq --help
- Run against the project in the current directory:
xcq 'targets { name type }' - Or specify a project:
xcq 'targets { name }' --project MyApp.xcodeproj
- Start interactive mode:
xcq interactive [--project PATH] [--debounce MS] [--color|--no-color] - Behavior:
- On a TTY, shows a single-line prompt at the bottom and a live preview above it.
- Evaluates your query as you type (debounced; default 200ms) and renders pretty JSON.
- Errors are shown inline in the preview area; press ESC or Ctrl+C to exit; Ctrl+U clears the line.
- In non-TTY environments (e.g., piped input), reads line-by-line from stdin and prints pretty JSON.
- Output is always pretty-printed JSON in interactive mode.
- Press Tab to show context-aware suggestions; Tab again hides the panel.
- Up/Down navigates; Enter/Right accepts the selected suggestion; ESC exits interactive mode.
- Supported contexts (driven by the built-in schema):
- Top-level fields at root, object fields inside selections, and argument names inside
(...). - Enum values for enum-typed arguments (e.g.,
TargetType,ScriptStage). - Filter keys inside inputs:
TargetFilter(name,type),SourceFilter/ResourceFilter(path,target),BuildScriptFilter(stage,name,target). - Nested
StringMatchkeys:eq,regex,prefix,suffix,contains.
- Top-level fields at root, object fields inside selections, and argument names inside
- Examples (position cursor at
•and press Tab):targets(filter: { • }) { name }→name,typetargets(filter: { type: • }) { name }→APP,FRAMEWORK, …targetResources(filter: { path: { • } }) { target path }→eq,regex,prefix,suffix,contains
SchemaCommand renders from the GraphQLSwift runtime schema (single source of truth).
Top-level fields (selection required):
targets(type: TargetType, filter: TargetFilter): [Target!]!target(name: String!): Targetdependencies(name: String!, recursive: Boolean = false, filter: TargetFilter): [Target!]!dependents(name: String!, recursive: Boolean = false, filter: TargetFilter): [Target!]!- Flat views:
targetSources(pathMode: PathMode = FILE_REF, filter: SourceFilter): [TargetSource!]!targetResources(pathMode: PathMode = FILE_REF, filter: ResourceFilter): [TargetResource!]!targetDependencies(recursive: Boolean = false, filter: TargetFilter): [TargetDependency!]!targetBuildScripts(filter: BuildScriptFilter): [TargetBuildScript!]!targetMembership(path: String!, pathMode: PathMode = FILE_REF): TargetMembership!
Types and inputs:
type Target { name, type, dependencies(recursive, filter), sources(pathMode, filter), resources(pathMode, filter), buildScripts(filter) }type BuildScript { name, stage, inputPaths, outputPaths, inputFileListPaths, outputFileListPaths }- Views:
TargetSource { target, path },TargetResource { target, path },TargetDependency { target, name, type },TargetBuildScript { target, ... },TargetMembership { path, targets } enum TargetType { APP, FRAMEWORK, STATIC_LIBRARY, DYNAMIC_LIBRARY, UNIT_TEST, UI_TEST, EXTENSION, BUNDLE, COMMAND_LINE_TOOL, WATCH_APP, WATCH2_APP, TV_APP, OTHER }enum PathMode { FILE_REF, ABSOLUTE, NORMALIZED }enum ScriptStage { PRE, POST }- Filters:
input TargetFilter { name: StringMatch, type: TargetType }input SourceFilter { path: StringMatch, target: StringMatch }input ResourceFilter { path: StringMatch, target: StringMatch }input BuildScriptFilter { stage: ScriptStage, name: StringMatch, target: StringMatch }input StringMatch { eq: String, regex: String, prefix: String, suffix: String, contains: String }
-
List targets and types:
xcq 'targets { name type }'
-
Unit test targets only:
xcq 'targets(type: UNIT_TEST) { name }'
-
Targets with name ending in "Tests":
xcq 'targets(filter: { name: { suffix: "Tests" } }) { name }'
-
Dependencies of a target:
- Direct:
xcq 'dependencies(name: "App") { name type }' - Transitive:
xcq 'dependencies(name: "App", recursive: true) { name }' - Reverse (who depends on):
xcq 'dependents(name: "Lib") { name }'
- Direct:
-
Per-target dependencies (nested):
xcq 'targets(type: UNIT_TEST) { name dependencies(recursive: true) { name } }'
-
Sources
- Nested, normalized Swift only:
xcq 'targets(type: FRAMEWORK) { name sources(pathMode: NORMALIZED, filter: { path: { regex: "\\.swift$" }}) { path } }'
- Flat, normalized (easy to pipe):
xcq 'targetSources(pathMode: NORMALIZED) { target path }'
- Nested, normalized Swift only:
-
Resources (Copy Bundle Resources)
- Per-target JSON resources:
xcq 'targets { name resources(filter: { path: { regex: "\\.json$" }}) { path } }'
- Flat list, exact filename:
xcq 'targetResources { target path }' | jq '.targetResources | map(select(.path == "Info.plist"))'
- Per-target JSON resources:
-
Build scripts
- Nested for frameworks, pre stage:
xcq 'targets(type: FRAMEWORK) { name buildScripts(filter: { stage: PRE }) { name stage inputPaths } }'
- Flat with stage filter:
xcq 'targetBuildScripts(filter: { stage: PRE }) { target name stage }'
- Nested for frameworks, pre stage:
-
Target membership for a file
xcq 'targetMembership(path: "Shared/Shared.swift", pathMode: NORMALIZED) { path targets }'
-
Files used by multiple targets (normalized):
xcq 'targetSources(pathMode: NORMALIZED) { target path }' --project MyApp.xcodeproj | jq '.targetSources | group_by(.path) | map(select(length > 1) | { path: .[0].path, targets: map(.target) })'
-
Files not in any target (absolute):
find "$(pwd)" \( -name "*.swift" -o -name "*.m" -o -name "*.mm" -o -name "*.c" -o -name "*.cc" -o -name "*.cpp" \) -not -path "$(pwd)/.build/*" -not -path "$(pwd)/**/*.xcodeproj/*" -print0 | xargs -0 -n1 -I{} sh -c 'xcq "targetMembership(path: \"{}\", pathMode: ABSOLUTE) { path targets }" --project MyApp.xcodeproj' | jq -s '.[].targetMembership | select(.targets | length == 0)'
- Path formatting is explicit via
pathModeon fields or flat views. No global state. - Regex uses
NSRegularExpressionand is case‑sensitive. - Arrays are sorted by sensible defaults (names/paths) unless further ordering is added later.
-
Create a version tag, e.g.
v0.1.0, and push it:git tag v0.1.0 && git push origin v0.1.0
-
GitHub Actions will automatically:
- Build the release binary for macOS
- Publish the GitHub Release (idempotent via
gh), uploadingxcq-v0.1.0-macos.zip - Update the
alvarhansen/homebrew-xcodequerytap to install the prebuilt binary
-
Users install via the tap:
- Stable:
brew tap alvarhansen/xcodequery && brew install xcq - HEAD:
brew tap alvarhansen/xcodequery && brew install --HEAD xcq
- Stable:
Once the alvarhansen/homebrew-xcodequery tap has the generated formula, users can install via:
brew tap alvarhansen/xcodequerybrew install xcq