fix(ioscompanion): key the device build cache on signing identity

The cache marker hashed only sources, so switching signing team or key
reused a runner signed with the stale identity, which the device rejects at
install (0xe8008018). Fold team + key id into the cache key so a signing
change forces a rebuild.
This commit is contained in:
pj committed 2026-06-09 14:46:54 +05:30
1 parent 0a48502ab8
commit ac76fcca86
2 files changed
+47 -4

No files matched your search

+16 -4
View File
@@ -162,16 +162,16 @@ func (d *Driver) realSpawnDeviceRunner(ctx context.Context, address string) (*ex
}
// buildDeviceRunnerIfNeeded regenerates the project and runs build-for-testing,
// skipping the build when a marker recording the current source hash already
// skipping the build when a marker recording the current build key already
// matches. The device signature is per-account/per-device, so the build cannot
// be embedded; the stable derivedDataPath makes the build incremental.
func (d *Driver) buildDeviceRunnerIfNeeded(ctx context.Context, companionDir, derivedDataPath string, creds signingCredentials) error {
sources, err := sourceHash(companionDir)
key, err := buildCacheKey(companionDir, creds)
if err != nil {
return err
}
marker := filepath.Join(derivedDataPath, "device-runner.sha256")
if existing, readErr := os.ReadFile(marker); readErr == nil && string(existing) == sources {
if existing, readErr := os.ReadFile(marker); readErr == nil && string(existing) == key {
fmt.Fprintln(d.output, "device runner build is up to date; skipping build")
return nil
}
@@ -186,12 +186,24 @@ func (d *Driver) buildDeviceRunnerIfNeeded(ctx context.Context, companionDir, de
if out, buildErr := runQuiet(ctx, companionDir, append([]string{"xcrun"}, args...)...); buildErr != nil {
return fmt.Errorf("build-for-testing: %w: %s", buildErr, tailLines(string(out), 20))
}
if err := os.WriteFile(marker, []byte(sources), 0o644); err != nil {
if err := os.WriteFile(marker, []byte(key), 0o644); err != nil {
return err
}
return nil
}
// buildCacheKey combines the source hash with the signing identity so a changed
// team or key invalidates the cached build. A runner signed with a stale
// identity would otherwise be reused and rejected at install (0xe8008018).
func buildCacheKey(companionDir string, creds signingCredentials) (string, error) {
sources, err := sourceHash(companionDir)
if err != nil {
return "", err
}
sum := sha256.Sum256([]byte(sources + "\x00" + creds.team + "\x00" + creds.authKeyID))
return hex.EncodeToString(sum[:]), nil
}
// xcodegenArgs regenerates the runner project from its spec.
func xcodegenArgs(specPath string) []string {
return []string{"xcodegen", "--spec", specPath}
@@ -168,6 +168,37 @@ func TestReadSigningCredentialsResolvesRelativeKeyPath(t *testing.T) {
}
}
func TestBuildCacheKeyChangesWithSigningIdentity(t *testing.T) {
dir := t.TempDir()
if err := os.MkdirAll(filepath.Join(dir, "Sources"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "Sources", "Server.swift"), []byte("v1"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "project.yml"), []byte("name: x"), 0o644); err != nil {
t.Fatal(err)
}
base, err := buildCacheKey(dir, signingCredentials{team: "TEAM1", authKeyID: "KID1"})
if err != nil {
t.Fatal(err)
}
otherTeam, err := buildCacheKey(dir, signingCredentials{team: "TEAM2", authKeyID: "KID1"})
if err != nil {
t.Fatal(err)
}
otherKey, err := buildCacheKey(dir, signingCredentials{team: "TEAM1", authKeyID: "KID2"})
if err != nil {
t.Fatal(err)
}
if base == otherTeam {
t.Fatal("a changed team must invalidate the cached build")
}
if base == otherKey {
t.Fatal("a changed signing key must invalidate the cached build")
}
}
func TestSourceHashChangesWithSources(t *testing.T) {
dir := t.TempDir()
if err := os.MkdirAll(filepath.Join(dir, "Sources"), 0o755); err != nil {