diff --git a/.github/instructions/gradle.instructions.md b/.github/instructions/gradle.instructions.md new file mode 100644 index 00000000000..7233969afe9 --- /dev/null +++ b/.github/instructions/gradle.instructions.md @@ -0,0 +1,49 @@ +--- +applyTo: "**/*.gradle" +--- + +# Gradle conventions + +All `src/*` Gradle projects share two repo config files: **`eng/gradle/plugin-repositories.gradle`** (for `pluginManagement.repositories`) and **`eng/gradle/dependency-repositories.gradle`** (for `dependencyResolutionManagement.repositories`). Never hard-code Maven URLs (`mavenCentral()`, `google()`, `pkgs.dev.azure.com/...`, etc.) in `build.gradle`/`settings.gradle`. + +## settings.gradle template + +```groovy +pluginManagement { + apply from: "${rootDir}/../../eng/gradle/plugin-repositories.gradle", to: pluginManagement +} +plugins { + id 'com.microsoft.azure.artifacts.credprovider' version '1.1.1' +} +dependencyResolutionManagement { + apply from: "${rootDir}/../../eng/gradle/dependency-repositories.gradle", to: dependencyResolutionManagement +} +rootProject.name = '' +``` + +`build.gradle` files must not declare their own `repositories { ... }`. + +## CI vs local + +Both files switch on `System.getenv('RunningOnCI')` (or `RUNNINGONCI` — AzDO uppercases env vars on Linux/macOS agents): + +- **`RunningOnCI=true`** (Azure DevOps, set in `build-tools/automation/yaml-templates/variables.yaml`) → dnceng `dotnet-public-maven` feed (CFSClean isolation, https://aka.ms/1es/netiso/CFS). Anonymous read of cached packages. +- **unset** (local, Dependabot, GitHub Actions) → `google()` + `mavenCentral()` + `gradlePluginPortal()` for plugins, `google()` + `mavenCentral()` for deps. No credentials needed. + +Test the CI path locally: `$env:RunningOnCI='true'` (PowerShell) or `RunningOnCI=true ...` (bash). + +## When CI fails 401 on a Dependabot bump + +The new package isn't cached in the feed yet. One-time setup, then ingest: + +1. `iex "& { $(irm https://aka.ms/install-artifacts-credprovider.ps1) }"` (or the `.sh` equivalent) +2. `$env:RunningOnCI='true'; ./build-tools/gradle/gradlew.bat --project-dir src/ build` — sign in via the device-flow prompt; the feed proxies + caches the package. +3. Re-run CI on the Dependabot PR. No PR edit needed. + +The credprovider plugin is a no-op when no AzDO repos are configured (i.e. local builds without `RunningOnCI`). + +## Don'ts + +- Don't hard-code Maven repo URLs in `build.gradle` / `settings.gradle`; use the shared file. +- Don't wrap `plugins {}` in `if (...)` — Gradle rejects it. +- Don't use modern `plugins { id 'com.android.application' version '...' }` DSL without confirming the plugin is in `dotnet-public-maven`; prefer `buildscript { ... } / apply plugin: '...'` when in doubt. \ No newline at end of file diff --git a/eng/gradle/dependency-repositories.gradle b/eng/gradle/dependency-repositories.gradle new file mode 100644 index 00000000000..ac491c649c2 --- /dev/null +++ b/eng/gradle/dependency-repositories.gradle @@ -0,0 +1,23 @@ +// Shared Maven repository list for project DEPENDENCY resolution +// (dependencyResolutionManagement.repositories) across every settings.gradle +// in this repo. See plugin-repositories.gradle for plugin resolution. +// +// Switches on RunningOnCI for the same CFSClean reasons described there. +// AzureArtifacts is intentionally NOT included here — it only hosts the +// credprovider plugin, so listing it in this scope would add a 404 round-trip +// to every dependency lookup. + +repositories { + // AzDO uppercases pipeline variables when exporting them as env vars on + // Linux/macOS agents, so check both spellings. + def runningOnCI = System.getenv('RunningOnCI') ?: System.getenv('RUNNINGONCI') + if (runningOnCI == 'true') { + maven { + url = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-maven/maven/v1' + name = 'dotnet-public-maven' + } + } else { + google() + mavenCentral() + } +} diff --git a/eng/gradle/plugin-repositories.gradle b/eng/gradle/plugin-repositories.gradle new file mode 100644 index 00000000000..bca6736b23f --- /dev/null +++ b/eng/gradle/plugin-repositories.gradle @@ -0,0 +1,68 @@ +// Shared Maven repository list for PLUGIN resolution (pluginManagement.repositories) +// across every settings.gradle in this repo. See plugin-repositories.gradle's +// sibling, dependency-repositories.gradle, for project dependency resolution. +// +// In our Azure DevOps CI pipeline (RunningOnCI=true), plugins resolve through +// the dnceng Azure Artifacts feed (dotnet-public-maven) for CFSClean network +// isolation compliance (https://aka.ms/1es/netiso/CFS). Locally and from +// GitHub Actions (e.g. Dependabot), the standard Gradle Plugin Portal is used. +// +// AzureArtifacts (anonymous public feed) is always included because every +// settings.gradle loads the artifacts-credprovider plugin from there. +// +// The dnceng feed proxies public sources. Once any package has been pulled +// through the feed (an authenticated request), it is cached and anonymous +// reads work forever after. CI therefore does NOT need credentials — it just +// reads anonymously from packages already cached in the feed. +// +// =================== TESTING / INGESTING LOCALLY =================== +// +// To exercise the CI code path locally (or to ingest a new package that +// Dependabot brought in but isn't yet cached in the feed): +// +// 1. Install the Azure Artifacts credential provider (one-time): +// +// PowerShell: iex "& { $(irm https://aka.ms/install-artifacts-credprovider.ps1) }" +// bash: wget -qO- https://aka.ms/install-artifacts-credprovider.sh | bash +// +// 2. Flip the switch and run the gradle build that needs the package: +// +// PowerShell: $env:RunningOnCI='true'; ./build-tools/gradle/gradlew.bat --project-dir src/r8 build +// bash: RunningOnCI=true ./build-tools/gradle/gradlew --project-dir src/r8 build +// +// On first authenticated request, you'll get a device-flow login prompt +// pointing at https://aka.ms/devicelogin — sign in with your Microsoft +// account. The credprovider caches the token; the feed caches the +// package; future CI runs read it anonymously and pass. +// +// =================== WORKFLOW FOR DEPENDABOT PRs =================== +// +// 1. Dependabot opens a PR bumping a Gradle dep (uses public repos, so it +// always sees the latest upstream version). +// 2. CI runs with RunningOnCI=true, hits the feed, and fails with 401 if +// the new package version isn't ingested yet. +// 3. A maintainer follows the steps above to ingest the package, then +// re-runs CI. No PR edit is required. + +repositories { + // Anonymous public Azure Artifacts feed that hosts the + // artifacts-credprovider Gradle plugin (loaded by every settings.gradle). + maven { + url = 'https://pkgs.dev.azure.com/artifacts-public/PublicTools/_packaging/AzureArtifacts/maven/v1' + name = 'AzureArtifacts' + } + + // AzDO uppercases pipeline variables when exporting them as env vars on + // Linux/macOS agents, so check both spellings. + def runningOnCI = System.getenv('RunningOnCI') ?: System.getenv('RUNNINGONCI') + if (runningOnCI == 'true') { + maven { + url = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-maven/maven/v1' + name = 'dotnet-public-maven' + } + } else { + google() + mavenCentral() + gradlePluginPortal() + } +} diff --git a/src/manifestmerger/build.gradle b/src/manifestmerger/build.gradle index 61a060fa37d..8dc4acc4de5 100644 --- a/src/manifestmerger/build.gradle +++ b/src/manifestmerger/build.gradle @@ -10,13 +10,6 @@ java { targetCompatibility = ext.javaTargetVer } -repositories { - maven { url 'https://maven.google.com' } - mavenCentral() - maven { url 'https://kotlin.bintray.com/kotlinx' } - jcenter() -} - dependencies { // https://mvnrepository.com/artifact/com.android.tools.build/manifest-merger implementation 'com.android.tools.build:manifest-merger:32.2.1' diff --git a/src/manifestmerger/settings.gradle b/src/manifestmerger/settings.gradle index 6a4458f7677..483063983cd 100644 --- a/src/manifestmerger/settings.gradle +++ b/src/manifestmerger/settings.gradle @@ -1 +1,14 @@ +// See: eng/gradle/plugin-repositories.gradle, eng/gradle/dependency-repositories.gradle +pluginManagement { + apply from: "${rootDir}/../../eng/gradle/plugin-repositories.gradle", to: pluginManagement +} + +plugins { + id 'com.microsoft.azure.artifacts.credprovider' version '1.1.1' +} + +dependencyResolutionManagement { + apply from: "${rootDir}/../../eng/gradle/dependency-repositories.gradle", to: dependencyResolutionManagement +} + rootProject.name = 'manifestmerger' \ No newline at end of file diff --git a/src/proguard-android/build.gradle b/src/proguard-android/build.gradle index b168ffa79e6..9be3b1b1e74 100644 --- a/src/proguard-android/build.gradle +++ b/src/proguard-android/build.gradle @@ -2,11 +2,6 @@ plugins { id 'com.android.application' version '8.7.0' } -repositories { - google() - mavenCentral() -} - android { namespace 'com.microsoft.proguard.android' // Setting the minimum we support at the moment, might not matter diff --git a/src/proguard-android/settings.gradle b/src/proguard-android/settings.gradle index 958c11d72b5..f1d5c0e6326 100644 --- a/src/proguard-android/settings.gradle +++ b/src/proguard-android/settings.gradle @@ -1,8 +1,14 @@ +// See: eng/gradle/plugin-repositories.gradle, eng/gradle/dependency-repositories.gradle pluginManagement { - repositories { - gradlePluginPortal() - google() - mavenCentral() - } + apply from: "${rootDir}/../../eng/gradle/plugin-repositories.gradle", to: pluginManagement } + +plugins { + id 'com.microsoft.azure.artifacts.credprovider' version '1.1.1' +} + +dependencyResolutionManagement { + apply from: "${rootDir}/../../eng/gradle/dependency-repositories.gradle", to: dependencyResolutionManagement +} + rootProject.name = 'proguard-android' \ No newline at end of file diff --git a/src/r8/build.gradle b/src/r8/build.gradle index 5c375e55438..14a1244cebc 100644 --- a/src/r8/build.gradle +++ b/src/r8/build.gradle @@ -9,11 +9,6 @@ java { targetCompatibility = ext.javaTargetVer } -repositories { - google() - mavenCentral() -} - dependencies { implementation 'com.android.tools:r8:9.1.31' } diff --git a/src/r8/settings.gradle b/src/r8/settings.gradle new file mode 100644 index 00000000000..0d56e34025f --- /dev/null +++ b/src/r8/settings.gradle @@ -0,0 +1,14 @@ +// See: eng/gradle/plugin-repositories.gradle, eng/gradle/dependency-repositories.gradle +pluginManagement { + apply from: "${rootDir}/../../eng/gradle/plugin-repositories.gradle", to: pluginManagement +} + +plugins { + id 'com.microsoft.azure.artifacts.credprovider' version '1.1.1' +} + +dependencyResolutionManagement { + apply from: "${rootDir}/../../eng/gradle/dependency-repositories.gradle", to: dependencyResolutionManagement +} + +rootProject.name = 'r8' \ No newline at end of file