Skip to content

Commit a7b082d

Browse files
committed
Introduce radiography command-line tool
1 parent 8f1a560 commit a7b082d

File tree

9 files changed

+418
-2
lines changed

9 files changed

+418
-2
lines changed

RELEASING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ git commit -am "Prepare {NEW_VERSION} release" && \
2727
./gradlew clean && \
2828
./gradlew build && \
2929
./gradlew connectedCheck && \
30+
./gradlew :stoic-plugin:dist && \
3031
git tag v{NEW_VERSION} && \
3132
git push origin v{NEW_VERSION} && \
3233
gh workflow run publish-release.yml --ref v{NEW_VERSION} && \
@@ -37,7 +38,7 @@ git merge --no-ff --no-edit release_{NEW_VERSION} && \
3738
sed -i '' 's/VERSION_NAME={NEW_VERSION}/VERSION_NAME={NEXT_VERSION}-SNAPSHOT/' gradle.properties && \
3839
git commit -am "Prepare for next development iteration" && \
3940
git push && \
40-
gh release create v{NEW_VERSION} --title v{NEW_VERSION} --notes 'See [Change Log](https://github.com/square/radiography/blob/main/CHANGELOG.md)'
41+
gh release create v{NEW_VERSION} --title v{NEW_VERSION} --notes 'See [Change Log](https://github.com/square/radiography/blob/main/CHANGELOG.md)' stoic-plugin/build/distributions/radiography-stoic-plugin-{NEW_VERSION}.tar.gz
4142
```
4243

4344
* Wait for the release to be available [on Maven Central](https://repo1.maven.org/maven2/com/squareup/radiography/radiography/).

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@ apply(plugin = "binary-compatibility-validator")
5555

5656
extensions.configure<ApiValidationExtension> {
5757
// Ignore all sample projects, since they're not part of our API.
58+
// stoic-plugin builds a plugin to use with stoic - it's also not part of our API.
5859
// Only leaf project name is valid configuration, and every project must be individually ignored.
5960
// See https://github.com/Kotlin/binary-compatibility-validator/issues/3
6061
ignoredProjects = mutableSetOf(
6162
"compose-tests",
6263
"compose-unsupported-tests",
6364
"sample",
6465
"sample-compose",
66+
"stoic-plugin",
6567
)
6668
}
6769

settings.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ include(
2020
":compose-unsupported-tests",
2121
":radiography",
2222
":sample",
23-
":sample-compose"
23+
":sample-compose",
24+
":stoic-plugin"
2425
)

stoic-plugin/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Radiography Stoic Plugin
2+
3+
A [Stoic](https://github.com/block/stoic) plugin that dumps view hierarchies from running Android apps.
4+
5+
The plugin bundles Radiography and all its dependencies (~850KB APK), so target apps don't need them.
6+
7+
## Build
8+
9+
```bash
10+
../gradlew assembleDebug # APK only
11+
../gradlew dist # Full distribution package
12+
```
13+
14+
Distribution output: `build/distributions/radiography-stoic-plugin-{version}.tar.gz`
15+
16+
Version is read from `../gradle.properties` to stay in sync with the main library.
17+
18+
## How it works
19+
20+
When invoked via Stoic, the APK's `main()` function calls `Radiography.scan()` on the main thread and prints the view hierarchy to stdout.
21+
22+
See [prebuilt](prebuilt) for the user-facing README and CLI wrapper script.

stoic-plugin/build.gradle.kts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2020 Square Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
plugins {
17+
id("com.android.application")
18+
kotlin("android")
19+
}
20+
21+
android {
22+
namespace = "radiography.stoic"
23+
compileSdk = 35
24+
25+
defaultConfig {
26+
applicationId = "radiography.stoic"
27+
minSdk = 26
28+
targetSdk = 35
29+
versionCode = 1
30+
versionName = project.property("VERSION_NAME") as String
31+
}
32+
33+
compileOptions {
34+
sourceCompatibility = JavaVersion.VERSION_1_8
35+
targetCompatibility = JavaVersion.VERSION_1_8
36+
}
37+
}
38+
39+
dependencies {
40+
compileOnly("com.squareup.stoic:plugin-sdk:0.7.0")
41+
implementation(kotlin("stdlib"))
42+
implementation(project(":radiography"))
43+
}
44+
45+
// Task to create a distribution tar.gz
46+
tasks.register<Tar>("dist") {
47+
dependsOn("assembleDebug")
48+
49+
archiveBaseName.set("radiography-stoic-plugin")
50+
archiveVersion.set(project.property("VERSION_NAME") as String)
51+
compression = Compression.GZIP
52+
archiveExtension.set("tar.gz")
53+
54+
into("radiography-stoic-plugin") {
55+
// Include the APK
56+
from("${layout.buildDirectory.get()}/outputs/apk/debug") {
57+
include("stoic-plugin-debug.apk")
58+
}
59+
60+
// Include the wrapper script and README from prebuilt
61+
from("$projectDir/prebuilt") {
62+
include("radiography")
63+
include("README.md")
64+
filePermissions {
65+
unix("rwxr-xr-x")
66+
}
67+
}
68+
}
69+
70+
destinationDirectory.set(layout.buildDirectory.dir("distributions"))
71+
}
72+
73+
// Convenience alias
74+
tasks.register("distribution") {
75+
dependsOn("dist")
76+
}

stoic-plugin/prebuilt/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Radiography CLI
2+
3+
Print the view hierarchy of any running Android app.
4+
5+
## Install
6+
7+
```bash
8+
tar -xzf radiography-stoic-plugin-2.8-SNAPSHOT.tar.gz
9+
export PATH="$PWD/radiography-stoic-plugin:$PATH"
10+
```
11+
12+
Or install globally:
13+
14+
```bash
15+
sudo tar -xzf radiography-stoic-plugin-2.8-SNAPSHOT.tar.gz -C /usr/local/bin --strip-components=1
16+
```
17+
18+
## Usage
19+
20+
```bash
21+
# Scan app
22+
radiography com.example.myapp
23+
24+
# Include text content
25+
radiography --pii com.example.myapp
26+
27+
# Scan only focused window
28+
radiography --focused com.example.myapp
29+
30+
# Specific device
31+
radiography -s emulator-5554 com.example.myapp
32+
```
33+
34+
## Options
35+
36+
- `--pii` - Include text content
37+
- `--focused` - Scan only focused window
38+
- `-s SERIAL` - Use specific device
39+
- `-d` - Use USB device
40+
- `-e` - Use emulator
41+
42+
## Requirements
43+
44+
- [Stoic](https://github.com/block/stoic) installed
45+
- Target app must be debuggable
46+
- Android SDK 26+
47+
48+
---
49+
50+
Built on [Radiography](https://github.com/square/radiography) and [Stoic](https://github.com/block/stoic)

stoic-plugin/prebuilt/radiography

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Radiography - View Hierarchy Scanner for Android
4+
# A wrapper script for the Radiography Stoic plugin
5+
#
6+
7+
set -e
8+
9+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
PLUGIN_APK="${SCRIPT_DIR}/stoic-plugin-debug.apk"
11+
12+
# Check if stoic is installed
13+
if ! command -v stoic &> /dev/null; then
14+
echo "Error: 'stoic' command not found. Please install Stoic first." >&2
15+
echo "See: https://github.com/block/stoic" >&2
16+
exit 1
17+
fi
18+
19+
# Check if plugin APK exists
20+
if [[ ! -f "${PLUGIN_APK}" ]]; then
21+
echo "Error: Plugin APK not found at ${PLUGIN_APK}" >&2
22+
exit 1
23+
fi
24+
25+
# Function to display usage
26+
usage() {
27+
cat << EOF
28+
Usage: radiography [OPTIONS] [PACKAGE]
29+
30+
Scan and print the view hierarchy of a running Android application using Radiography.
31+
32+
Arguments:
33+
PACKAGE Optional. The package name of the app to inspect.
34+
Can be specified as a positional argument or via --pkg/--package/-n.
35+
If not specified, Stoic will try to detect the foreground app.
36+
37+
Options:
38+
--pii Include PII (personally identifiable information) like text content
39+
--focused Scan only the focused window instead of all windows
40+
--pkg PACKAGE Specify the package name to inspect (same as -n, --package)
41+
--package PACKAGE Specify the package name to inspect (same as -n, --pkg)
42+
-n PACKAGE Specify the package name to inspect (same as --pkg, --package)
43+
-h, --help Display this help message
44+
45+
Device Selection (passed through to adb):
46+
-s SERIAL Use device with given serial number
47+
-d Use USB device
48+
-e Use emulator
49+
50+
Examples:
51+
# Scan the foreground app
52+
radiography
53+
54+
# Scan a specific app
55+
radiography com.example.myapp
56+
57+
# Scan with PII included
58+
radiography --pii com.example.myapp
59+
60+
# Scan only focused window
61+
radiography --focused com.example.myapp
62+
63+
# Scan on specific device
64+
radiography -s emulator-5554 com.example.myapp
65+
66+
# Use emulator
67+
radiography -e com.example.myapp
68+
69+
# Combine options
70+
radiography --pii --focused -s emulator-5554 com.example.myapp
71+
72+
For more information about Stoic, see: https://github.com/block/stoic
73+
For more information about Radiography, see: https://github.com/square/radiography
74+
EOF
75+
}
76+
77+
# Parse arguments
78+
PLUGIN_ARGS=()
79+
STOIC_ARGS=()
80+
PACKAGE=""
81+
82+
while [[ $# -gt 0 ]]; do
83+
case $1 in
84+
-h|--help)
85+
usage
86+
exit 0
87+
;;
88+
--pii|--focused)
89+
# Plugin arguments
90+
PLUGIN_ARGS+=("$1")
91+
shift
92+
;;
93+
--pkg|--package|-n)
94+
# Package name arguments (passed through to stoic)
95+
if [[ $# -lt 2 ]]; then
96+
echo "Error: $1 requires a package name" >&2
97+
exit 1
98+
fi
99+
if [[ -n "${PACKAGE}" ]]; then
100+
echo "Error: Package already specified as '${PACKAGE}'" >&2
101+
exit 1
102+
fi
103+
PACKAGE_FLAG="$1"
104+
shift
105+
PACKAGE="$1"
106+
shift
107+
;;
108+
-s|-d|-e)
109+
# ADB device selection arguments (passed through to stoic/adb)
110+
STOIC_ARGS+=("$1")
111+
if [[ "$1" == "-s" ]]; then
112+
if [[ $# -lt 2 ]]; then
113+
echo "Error: -s requires a device serial number" >&2
114+
exit 1
115+
fi
116+
shift
117+
STOIC_ARGS+=("$1")
118+
fi
119+
shift
120+
;;
121+
-*)
122+
echo "Error: Unknown option: $1" >&2
123+
echo "Run 'radiography --help' for usage information." >&2
124+
exit 1
125+
;;
126+
*)
127+
# Assume it's the package name (positional argument)
128+
if [[ -n "${PACKAGE}" ]]; then
129+
echo "Error: Package already specified as '${PACKAGE}'" >&2
130+
exit 1
131+
fi
132+
PACKAGE="$1"
133+
PACKAGE_FLAG="--pkg" # Default to --pkg when using positional argument
134+
shift
135+
;;
136+
esac
137+
done
138+
139+
# Build the stoic command
140+
STOIC_CMD=(stoic "${STOIC_ARGS[@]}")
141+
142+
# Add package name if specified (preserve the flag the user used, or use --pkg as default)
143+
if [[ -n "${PACKAGE}" ]]; then
144+
STOIC_CMD+=("${PACKAGE_FLAG}" "${PACKAGE}")
145+
fi
146+
147+
# Add the plugin APK and its arguments
148+
STOIC_CMD+=("${PLUGIN_APK}" "${PLUGIN_ARGS[@]}")
149+
150+
# Execute stoic
151+
exec "${STOIC_CMD[@]}"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright 2020 Square Inc.
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
18+
<application />
19+
</manifest>

0 commit comments

Comments
 (0)