googlesamples / googlesamples/unity-jar-resolver

[Android Resolver] Case-sensitive path prefix checks silently break resolution when the project path's drive-letter casing differs (e.g. Jenkins c:\ workspaces)

Open
#776 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
1.5k
Forks
371
Avg merge
14h 21m
Merged PRs (30d)
3

Description

## Summary

On Windows, if the Unity project is opened via a path whose casing differs from the
OS-canonical casing — most commonly a CI workspace with a lowercase drive letter, e.g.
Jenkins' default `c:\jenkins\workspace\...` — the Android Resolver's path prefix checks
fail silently and resolution produces a broken Gradle configuration:

1. `.srcaar` artifacts shipped inside UPM packages are **not** copied/converted into the
local Maven repo (`GooglePlayServices.LocalMavenRepoDir`). No error or warning is logged;
the copy step simply finds nothing to do.
2. The Maven repo URLs injected into `settingsTemplate.gradle` / `mainTemplate.gradle`
point at the **raw immutable package cache**
(`file:///C:/.../Library/PackageCache/@/Firebase/m2repository`)
instead of the local Maven repo.
3. The Gradle build then fails, because the package cache m2repository contains only
`.srcaar` files (with unpatched POMs):

```
> Could not resolve all files for configuration ':launcher:releaseRuntimeClasspath'.
> Failed to transform firebase-analytics-unity-13.14.0.aar (com.google.firebase:firebase-analytics-unity:13.14.0)
> Could not find firebase-analytics-unity-13.14.0.jar (com.google.firebase:firebase-analytics-unity:13.14.0).
Searched in the following locations:
file:/C:/jenkins/workspace/MyProject/Library/PackageCache/com.google.firebase.analytics@78bf8c0ff89a/Firebase/m2repository/com/google/firebase/firebase-analytics-unity/13.14.0/firebase-analytics-unity-13.14.0.aar
file:/C:/jenkins/workspace/MyProject/Library/PackageCache/com.google.firebase.analytics@78bf8c0ff89a/Firebase/m2repository/com/google/firebase/firebase-analytics-unity/13.14.0/firebase-analytics-unity-13.14.0.jar
```

The same project resolves correctly on developer machines (canonically-cased paths), which
makes this look like a mysterious CI-only failure. It affects both interactive resolution
and `Resolution On Build` — anything that triggers `GradleTemplateResolver`.

## Environment

- External Dependency Manager for Unity: **1.2.188** (also reproduced on earlier 1.2.18x)
- Unity: 6000.4.12f1, Android Gradle Plugin 9.0.0, Gradle 9.1.0
- Windows agents (Jenkins), workspace root configured as `c:\jenkins` (lowercase — Jenkins
does not canonicalize; this is a very common agent configuration)
- Firebase Unity SDK 13.14.0 via UPM packages (`com.google.firebase.*`), which ship
`.srcaar` artifacts under `/Firebase/m2repository`, plus Google Play Games —
any package-embedded local Maven repo reproduces it
- `Patch mainTemplate.gradle` + `Patch settingsTemplate.gradle` enabled,
`LocalMavenRepoDir = Assets/GeneratedLocalRepo`

## Root cause

Unity reports Package Manager physical paths (`PackageInfo.resolvedPath` →
`Library/PackageCache/...`) with **canonical casing** (`C:\...`), while the project URI is
derived from the process working directory (`Path.GetFullPath(".")`), which **preserves the
casing the process was launched with** (`c:\...` when Unity is started with
`-projectPath c:\jenkins\...`). All prefix comparisons between the two are ordinal and
case-sensitive, so on such hosts every one of them fails:

- `LocalMavenRepository.FindLocalRepos` —
`repoUri.StartsWith(projectUri)` never matches, so `FindAarsInLocalRepos` returns an
empty list and `GradleTemplateResolver.CopySrcAars` **succeeds without copying anything**
(this is why there is no error in the log).
https://github.com/googlesamples/unity-jar-resolver/blob/v1.2.188/source/AndroidResolver/src/LocalMavenRepository.cs#L43
- `GradleTemplateResolver.GradleMavenReposLinesFromDependencies` —
`repoAndSources.Key.StartsWith(projectFileUri)` never matches, so every package-embedded
repo takes the "external repo" branch and is emitted as a raw absolute
`file:///C:/.../Library/PackageCache/...` URL instead of being remapped to
`LocalMavenRepoDir`.
https://github.com/googlesamples/unity-jar-resolver/blob/v1.2.188/source/AndroidResolver/src/GradleTemplateResolver.cs#L725

Direct evidence from a single generated `settings.gradle`, which contains the same project
path in **both casings** — Unity's own `**DIR_UNITYPROJECT**` substitution (lowercase, from
`-projectPath`) next to the resolver-injected URLs (uppercase, from Package Manager):

```gradle
def unityProjectPath = $/file:///c:/jenkins/workspace/MyProject/$.replace("\\", "/")
...
maven {
url "file:///C:/jenkins/workspace/MyProject/Library/PackageCache/com.google.firebase.app@70df0f53b04f/Firebase/m2repository" // Packages/com.google.firebase.app/Firebase/Editor/AppDependencies.xml:25
}
```

## Steps to reproduce

1. On Windows, place a Unity project that uses the Firebase UPM packages under
`c:\some\path` (note the lowercase drive letter).
2. Launch Unity with `-projectPath c:\some\path\project` (lowercase, exactly as Jenkins
passes `%WORKSPACE%`), with a clean/absent `LocalMavenRepoDir`.
3. Run **Assets > External Dependency Manager > Android Resolver > Force Resolve** (or
build with `Resolution On Build` enabled).
4. Observe: `LocalMavenRepoDir` is not populated, `settingsTemplate.gradle` receives
absolute `Library/PackageCache/...` repo URLs, and the Gradle build fails with
`Could not find .jar`.
5. Repeat with `-projectPath C:\Some\Path\Project` (canonical casing): resolution copies
the srcaars into `LocalMavenRepoDir`, injects
`(unityProjectPath + "/Assets/GeneratedLocalRepo/...")`, and the build succeeds.

We verified 4↔5 as a controlled A/B on the same machine/workspace (only the path casing
changed, via a canonically-cased junction-equivalent workspace path): lowercase = fail,
canonical = success.

Confirmed on two independent projects: one with Firebase only (UPM package repos), and one
with Firebase + Google Play Games (`Assets/GooglePlayGames/.../m2repository`, a
project-local repo). With canonical casing both repos are correctly regenerated into
`LocalMavenRepoDir` (`firebase-*-unity-13.14.0.aar` × 5 + `gpgs-plugin-support-2.1.0.aar`)
and both injected URLs point at the local repo; with lowercase casing the same builds fail
as described above.

## Suggested fix

Perform the project-prefix comparisons case-insensitively on case-insensitive filesystems
(or normalize both sides to canonical casing before comparing), e.g.:

```csharp
repoUri.StartsWith(projectUri, StringComparison.OrdinalIgnoreCase) // Windows/macOS
```

in `LocalMavenRepository.FindLocalRepos` and
`GradleTemplateResolver.GradleMavenReposLinesFromDependencies` (and any other
`StartsWith(projectUri/projectFileUri)` path checks). Alternatively normalize
`Path.GetFullPath(".")` to the canonical on-disk casing before building `projectFileUri`.

## Workaround

Ensure the casing of the path Unity is launched with matches the OS-canonical casing. On
Jenkins we wrap the build in `ws()` using the canonically-cased workspace path (obtained
via `Scripting.FileSystemObject.GetFolder(path).Path`), which fully restores correct
resolution behaviour.

## Related (different mechanism, similar symptom)

- #342 / #392 — older "unable to copy srcaar with UPM" issues (copy errors were logged;
here the copy is silently skipped)
- #709 — fixes for copying Android files from Packages

Contributor guide

Open the contributing guide

Research direction

Start with LocalMavenRepository.FindLocalRepos in source/AndroidResolver/src/LocalMavenRepository.cs and GradleTemplateResolver.GradleMavenReposLinesFromDependencies in source/AndroidResolver/src/GradleTemplateResolver.cs. Run the Windows reproduction using differently cased project paths and compare the generated local repository and Gradle URLs. Done means embedded .srcaar files are copied and repository URLs resolve through LocalMavenRepoDir for both casing variants.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, csharp, unity
Domain
build-system, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.