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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Telecom combines these classes with framework config_defaultDialer.
The package and class must both name components shipped by Eliza. -->
<string name="incall_default_class" translatable="false">ai.elizaos.app.ElizaInCallService</string>
<string name="dialer_default_class" translatable="false">ai.elizaos.app.ElizaDialActivity</string>
</resources>
84 changes: 80 additions & 4 deletions packages/os/scripts/__tests__/aosp-build-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import {
writeFile,
} from "node:fs/promises";
import { tmpdir as systemTmpdir } from "node:os";
import { delimiter, dirname, join } from "node:path";
import {
delimiter,
dirname,
join,
relative as relativePath,
resolve,
} from "node:path";
import { fileURLToPath } from "node:url";
import {
assertApkProvenanceEntries,
Expand Down Expand Up @@ -61,6 +67,7 @@ import {
closeAospBuildEnvironment,
cuttlefishLaunchCommand,
prepareAospBuildEnvironment,
rebuildPrivilegedApk,
resolveCuttlefishGpuMode,
revalidateAospBuildEnvironment,
} from "../../../../scripts/distro-android/build-aosp.mjs";
Expand Down Expand Up @@ -148,6 +155,57 @@ function tmpdir() {
}

describe("AOSP build contracts", () => {
test("privileged APK rebuild uses the current app checkout and preserves brand inputs and failures", async () => {
const root = await mkdtemp(join(tmpdir(), "elizaos-apk-source-"));
const receipt = join(root, "build-receipt.json");
const brand = {
envPrefix: "REVIEW",
packageName: "ai.review.app",
buildAndroidSystemCmd: [
process.execPath,
"-e",
`
require("node:fs").writeFileSync("build-receipt.json", JSON.stringify({
cwd: process.cwd(),
osRoot: process.env.ELIZAOS_OS_REPO_ROOT,
appId: process.env.REVIEW_APP_ID,
aospBuild: process.env.REVIEW_AOSP_BUILD,
gradleBuild: process.env.REVIEW_GRADLE_AOSP_BUILD,
}));
`,
],
};
try {
await mkdir(join(root, "packages/app-core"), { recursive: true });
await writeFile(join(root, "packages/app-core/package.json"), "{}");
expect(() => rebuildPrivilegedApk(brand, root)).toThrow(
"Set ELIZAOS_ELIZA_ROOT",
);
expect(existsSync(receipt)).toBe(false);
await mkdir(join(root, "packages/app"), { recursive: true });
await writeFile(join(root, "packages/app/package.json"), "{}");
rebuildPrivilegedApk(brand, root);
expect(JSON.parse(readFileSync(receipt, "utf8"))).toEqual({
cwd: root,
osRoot: resolve(repositoryRoot),
appId: "ai.review.app",
aospBuild: "1",
gradleBuild: "true",
});
expect(() =>
rebuildPrivilegedApk(
{
...brand,
buildAndroidSystemCmd: [process.execPath, "-e", "process.exit(23)"],
},
root,
),
).toThrow("exited with code 23");
} finally {
await rm(root, { recursive: true, force: true });
}
});

test("AOSP builds keep temporary artifacts on the default output volume", () => {
expect(
aospBuildEnvironment("/srv/aosp", {
Expand All @@ -162,6 +220,23 @@ describe("AOSP build contracts", () => {
});
});

test("AOSP default output uses a Siso-compatible relative path", async () => {
const root = await mkdtemp(join(tmpdir(), "elizaos-aosp-root-"));
let prepared: ReturnType<typeof prepareAospBuildEnvironment> | undefined;
try {
prepared = prepareAospBuildEnvironment(root, {});
expect(prepared.env.OUT_DIR).toBe("out");
expect(resolve(root, prepared.env.OUT_DIR)).toBe(
prepared.canonicalOutputRoot,
);
expect(prepared.env.TMPDIR).toBe(join(root, "out", ".elizaos-tmp"));
revalidateAospBuildEnvironment(prepared);
} finally {
if (prepared) closeAospBuildEnvironment(prepared);
await rm(root, { recursive: true, force: true });
}
});

test("AOSP build temporary artifacts follow absolute and relative OUT_DIR", async () => {
const root = await mkdtemp(join(tmpdir(), "elizaos-aosp-root-"));
const external = await mkdtemp(join(tmpdir(), "elizaos-aosp-out-"));
Expand All @@ -172,7 +247,8 @@ describe("AOSP build contracts", () => {
});
prepared.push(absolute);
expect(absolute.env.TMPDIR).toBe(join(external, ".elizaos-tmp"));
expect(absolute.env.OUT_DIR).toBe(external);
expect(absolute.env.OUT_DIR).toBe(relativePath(root, external));
expect(resolve(root, absolute.env.OUT_DIR)).toBe(external);

const relative = prepareAospBuildEnvironment(root, {
OUT_DIR: "build-output",
Expand All @@ -181,7 +257,7 @@ describe("AOSP build contracts", () => {
expect(relative.env.TMPDIR).toBe(
join(root, "build-output", ".elizaos-tmp"),
);
expect(relative.env.OUT_DIR).toBe(join(root, "build-output"));
expect(relative.env.OUT_DIR).toBe("build-output");
revalidateAospBuildEnvironment(absolute);
revalidateAospBuildEnvironment(relative);
} finally {
Expand All @@ -198,7 +274,7 @@ describe("AOSP build contracts", () => {
try {
await symlink(external, join(root, "out"), "dir");
prepared = prepareAospBuildEnvironment(root, {});
expect(prepared.env.OUT_DIR).toBe(external);
expect(prepared.env.OUT_DIR).toBe(relativePath(root, external));
expect(prepared.env.TMPDIR).toBe(join(external, ".elizaos-tmp"));
revalidateAospBuildEnvironment(prepared);
} finally {
Expand Down
11 changes: 5 additions & 6 deletions scripts/distro-android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ This directory contains the toolchain for building a brand-customised
Android AOSP image — Cuttlefish (virtual phone) for CI validation, and
real device targets (Pixel codenames) for installs.

The application build in `elizaOS/eliza` owns launcher icons and app splash
assets. This toolchain consumes its staged privileged APK.

The toolchain was originally written for **elizaOS** as a single
hardcoded brand and generalized so any brand can build a
privileged-system-app distribution by supplying a JSON brand config
Expand All @@ -22,7 +25,7 @@ and a corresponding **vendor tree** under `packages/os/android/vendor/<brand>/`.
"brand": "eliza", // lowercase token; vendor/<X>, init.<X>.rc, file paths
"appName": "Eliza", // PascalCase; APK module + apk filename
"distroName": "elizaOS", // brand display name in log messages
"packageName": "com.elizaai.eliza", // APK Java package id
"packageName": "ai.elizaos.app", // APK Java package id
"classPrefix": "Eliza", // Java class prefix (ElizaDialActivity, ElizaSmsReceiver, …)
"productName": "eliza_cf_x86_64_phone", // Cuttlefish product name + makefile filename stem
"lunchTarget": "eliza_cf_x86_64_phone-trunk_staging-userdebug",
Expand All @@ -34,11 +37,7 @@ and a corresponding **vendor tree** under `packages/os/android/vendor/<brand>/`.
"initRcName": "init.eliza.rc",
"commonMakefile":"eliza_common.mk",
"cuttlefishMakefile":"eliza_cf_x86_64_phone.mk",
"buildAndroidSystemCmd": ["bun", "run", "--cwd", "packages/app", "build:android:system"],

// Optional — only needed if the brand stores assets/cache outside the defaults
"androidAssetsDir": "packages/app-core/platforms/android/app/src/main/assets/agent",
"cacheDirName": "eliza-android-agent"
"buildAndroidSystemCmd": ["bun", "run", "--cwd", "packages/app", "build:android:system"]
}
```

Expand Down
12 changes: 8 additions & 4 deletions scripts/distro-android/build-aosp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,11 @@ export function prepareAospBuildEnvironment(aospRoot, env = process.env) {
throw error;
}

buildEnv.OUT_DIR = canonicalOutputRoot;
// Soong passes OUT_DIR/siso_config to Siso, whose config repository path
// must be relative to the source root. Keep canonical paths for ownership
// and identity checks, but give the build its equivalent relative path.
buildEnv.OUT_DIR =
path.relative(fs.realpathSync(aospRoot), canonicalOutputRoot) || ".";
buildEnv.TMPDIR = canonicalTemp;
buildEnv.TMP = canonicalTemp;
buildEnv.TEMP = canonicalTemp;
Expand Down Expand Up @@ -522,8 +526,8 @@ function launchCuttlefish(aospRoot, brand) {
* APK picks up BuildConfig.AOSP_BUILD=true and the agent bundle is
* produced with <BRAND>_AOSP_BUILD=1.
*/
function rebuildPrivilegedApk(brand) {
if (!fs.existsSync(path.join(elizaRoot, "packages/app-core/package.json"))) {
export function rebuildPrivilegedApk(brand, sourceRoot = elizaRoot) {
if (!fs.existsSync(path.join(sourceRoot, "packages/app/package.json"))) {
throw new Error(
"Set ELIZAOS_ELIZA_ROOT to an elizaOS/eliza checkout before rebuilding the privileged APK.",
);
Expand All @@ -537,7 +541,7 @@ function rebuildPrivilegedApk(brand) {
};
const [cmd, ...rest] = brand.buildAndroidSystemCmd;
const result = spawnSync(cmd, rest, {
cwd: elizaRoot,
cwd: sourceRoot,
env,
stdio: "inherit",
});
Expand Down
Loading