diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c98e8c79b..70b5080c3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: runs-on: ${{ matrix.os }} env: JAVA_TOOL_OPTIONS: -Xmx4g - timeout-minutes: 15 + timeout-minutes: ${{ matrix.timeout-minutes || 15 }} strategy: matrix: include: @@ -31,6 +31,7 @@ jobs: api-level: 31 target: default arch: x86_64 + timeout-minutes: 20 - os: ubuntu-24.04 api-level: 34 target: aosp_atd diff --git a/__tests__/sdk-installer.test.ts b/__tests__/sdk-installer.test.ts new file mode 100644 index 000000000..b89176de4 --- /dev/null +++ b/__tests__/sdk-installer.test.ts @@ -0,0 +1,26 @@ +import { parseCmdlineToolsMajorRevision } from '../src/sdk-installer'; + +describe('cmdline-tools revision parser tests', () => { + it('Parses the major revision of the cmdline-tools preinstalled on ubuntu-24.04', () => { + const sourceProperties = ['Pkg.UserSrc=false', 'Pkg.Revision=12.0', 'Pkg.Path=cmdline-tools;12.0', 'Pkg.Desc=Android SDK Command-line Tools', ''].join('\n'); + expect(parseCmdlineToolsMajorRevision(sourceProperties)).toBe(12); + }); + + it('Parses the major revision of the cmdline-tools bundled with the action', () => { + const sourceProperties = ['Pkg.UserSrc=false', 'Pkg.Revision=23.0', 'Pkg.Path=cmdline-tools;23.0', 'Pkg.Desc=Android SDK Command-line Tools', ''].join('\n'); + expect(parseCmdlineToolsMajorRevision(sourceProperties)).toBe(23); + }); + + it('Returns null if the revision is missing', () => { + const sourceProperties = ['Pkg.UserSrc=false', 'Pkg.Desc=Android SDK Command-line Tools', ''].join('\n'); + expect(parseCmdlineToolsMajorRevision(sourceProperties)).toBeNull(); + }); + + it('Returns null if the revision is malformed', () => { + expect(parseCmdlineToolsMajorRevision('Pkg.Revision=unknown')).toBeNull(); + }); + + it('Returns null for empty source properties', () => { + expect(parseCmdlineToolsMajorRevision('')).toBeNull(); + }); +}); diff --git a/lib/sdk-installer.js b/lib/sdk-installer.js index d84759889..e17676cc4 100644 --- a/lib/sdk-installer.js +++ b/lib/sdk-installer.js @@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); +exports.parseCmdlineToolsMajorRevision = parseCmdlineToolsMajorRevision; exports.installAndroidSdk = installAndroidSdk; const core = __importStar(require("@actions/core")); const exec = __importStar(require("@actions/exec")); @@ -44,6 +45,15 @@ const BUILD_TOOLS_VERSION = '37.0.0'; const CMDLINE_TOOLS_VERSION = '16111833'; const CMDLINE_TOOLS_URL_MAC = `https://dl.google.com/android/repository/commandlinetools-mac_x86_64-${CMDLINE_TOOLS_VERSION}_latest.zip`; const CMDLINE_TOOLS_URL_LINUX = `https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip`; +// keep in sync with CMDLINE_TOOLS_VERSION +const CMDLINE_TOOLS_MIN_MAJOR_REVISION = 23; +/** + * Returns the major revision of an installed cmdline-tools package from the content of its `source.properties`, or `null` if the revision cannot be determined. + */ +function parseCmdlineToolsMajorRevision(sourceProperties) { + const match = /^Pkg\.Revision=(\d+)/m.exec(sourceProperties); + return match ? Number(match[1]) : null; +} /** * Installs & updates the Android SDK for the macOS platform, including SDK platform for the chosen API level, latest build tools, platform tools, Android Emulator, * and the system image for the chosen API level, CPU arch, and target. @@ -54,10 +64,19 @@ async function installAndroidSdk(apiLevel, systemImageApiLevel, target, arch, ch const isOnMac = process.platform === 'darwin'; const isArm = process.arch === 'arm64'; const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`; - if (!fs.existsSync(cmdlineToolsPath)) { - console.log('Installing new cmdline-tools.'); + const sourcePropertiesPath = `${cmdlineToolsPath}/latest/source.properties`; + // a preinstalled cmdline-tools too old to parse minor API levels such as android-37.1 must be replaced, not kept + const installedRevision = fs.existsSync(sourcePropertiesPath) ? parseCmdlineToolsMajorRevision(fs.readFileSync(sourcePropertiesPath, 'utf8')) : null; + if (installedRevision === null || installedRevision < CMDLINE_TOOLS_MIN_MAJOR_REVISION) { + if (installedRevision === null) { + console.log('Installing new cmdline-tools.'); + } + else { + console.log(`Replacing cmdline-tools revision ${installedRevision} with revision ${CMDLINE_TOOLS_MIN_MAJOR_REVISION}.`); + } const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX; const downloadPath = await tc.downloadTool(sdkUrl); + await io.rmRF(`${cmdlineToolsPath}/latest`); await tc.extractZip(downloadPath, cmdlineToolsPath); await io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`); } diff --git a/src/sdk-installer.ts b/src/sdk-installer.ts index b284269d4..044156385 100644 --- a/src/sdk-installer.ts +++ b/src/sdk-installer.ts @@ -9,6 +9,16 @@ const BUILD_TOOLS_VERSION = '37.0.0'; const CMDLINE_TOOLS_VERSION = '16111833'; const CMDLINE_TOOLS_URL_MAC = `https://dl.google.com/android/repository/commandlinetools-mac_x86_64-${CMDLINE_TOOLS_VERSION}_latest.zip`; const CMDLINE_TOOLS_URL_LINUX = `https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip`; +// keep in sync with CMDLINE_TOOLS_VERSION +const CMDLINE_TOOLS_MIN_MAJOR_REVISION = 23; + +/** + * Returns the major revision of an installed cmdline-tools package from the content of its `source.properties`, or `null` if the revision cannot be determined. + */ +export function parseCmdlineToolsMajorRevision(sourceProperties: string): number | null { + const match = /^Pkg\.Revision=(\d+)/m.exec(sourceProperties); + return match ? Number(match[1]) : null; +} /** * Installs & updates the Android SDK for the macOS platform, including SDK platform for the chosen API level, latest build tools, platform tools, Android Emulator, @@ -30,10 +40,18 @@ export async function installAndroidSdk( const isArm = process.arch === 'arm64'; const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`; - if (!fs.existsSync(cmdlineToolsPath)) { - console.log('Installing new cmdline-tools.'); + const sourcePropertiesPath = `${cmdlineToolsPath}/latest/source.properties`; + // a preinstalled cmdline-tools too old to parse minor API levels such as android-37.1 must be replaced, not kept + const installedRevision = fs.existsSync(sourcePropertiesPath) ? parseCmdlineToolsMajorRevision(fs.readFileSync(sourcePropertiesPath, 'utf8')) : null; + if (installedRevision === null || installedRevision < CMDLINE_TOOLS_MIN_MAJOR_REVISION) { + if (installedRevision === null) { + console.log('Installing new cmdline-tools.'); + } else { + console.log(`Replacing cmdline-tools revision ${installedRevision} with revision ${CMDLINE_TOOLS_MIN_MAJOR_REVISION}.`); + } const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX; const downloadPath = await tc.downloadTool(sdkUrl); + await io.rmRF(`${cmdlineToolsPath}/latest`); await tc.extractZip(downloadPath, cmdlineToolsPath); await io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`); }