This is an automated email from the ASF dual-hosted git repository.

normanbreau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-android.git


The following commit(s) were added to refs/heads/master by this push:
     new 76bac55f fix: cordova requirements command and SDK lookup based on 
tools (#1877)
76bac55f is described below

commit 76bac55fba688f3ad472a0fa20362c58287066da
Author: Norman Breau <[email protected]>
AuthorDate: Thu Dec 18 09:23:01 2025 -0400

    fix: cordova requirements command and SDK lookup based on tools (#1877)
    
    * fix: cordova requirements command and SDK lookup based on tools
    
    * Update spec/unit/AndroidCommandLineTools.spec.js
    
    Co-authored-by: エリス <[email protected]>
    
    * Update lib/env/AndroidCommandLineTools.js
    
    Co-authored-by: エリス <[email protected]>
    
    * Update lib/env/AndroidCommandLineTools.js
    
    Co-authored-by: エリス <[email protected]>
    
    * Update lib/env/AndroidCommandLineTools.js
    
    ---------
    
    Co-authored-by: エリス <[email protected]>
---
 lib/check_reqs.js                         |  18 +++--
 lib/env/AndroidCommandLineTools.js        | 102 +++++++++++++++++++++++++++++
 spec/unit/AndroidCommandLineTools.spec.js | 105 ++++++++++++++++++++++++++++++
 spec/unit/check_reqs.spec.js              |  12 ++--
 4 files changed, 226 insertions(+), 11 deletions(-)

diff --git a/lib/check_reqs.js b/lib/check_reqs.js
index 6e1bfd4a..217f5dd6 100644
--- a/lib/check_reqs.js
+++ b/lib/check_reqs.js
@@ -24,6 +24,7 @@ const java = require('./env/java');
 const { CordovaError, ConfigParser, events } = require('cordova-common');
 const android_sdk = require('./android_sdk');
 const { SDK_VERSION } = require('./gradle-config-defaults');
+const AndroidCommandLineTools = require('./env/AndroidCommandLineTools');
 
 // Re-exporting these for backwards compatibility and for unit testing.
 // TODO: Remove uses and use the ./utils module directly.
@@ -216,14 +217,14 @@ module.exports.check_android = function () {
                 }
             }
             if (avdmanagerInPath) {
-                parentDir = path.dirname(avdmanagerInPath);
-                grandParentDir = path.dirname(parentDir);
-                if (path.basename(parentDir) === 'bin' && 
path.basename(grandParentDir) === 'tools') {
-                    maybeSetAndroidHome(path.dirname(grandParentDir));
+                let sdkPath = null;
+                if (/cmdline-tools/.test(avdmanagerInPath)) {
+                    sdkPath = path.resolve(avdmanagerInPath, '../../../..');
+                    maybeSetAndroidHome(sdkPath);
                 } else {
                     throw new CordovaError('Failed to find \'ANDROID_HOME\' 
environment variable. Try setting it manually.\n' +
-                        'Detected \'avdmanager\' command at ' + parentDir + ' 
but no \'tools' + path.sep + 'bin\' directory found near.\n' +
-                        'Try reinstall Android SDK or update your PATH to 
include valid path to SDK' + path.sep + 'tools' + path.sep + 'bin directory.');
+                        'Detected \'avdmanager\' command at ' + parentDir + ' 
but does not appear to be within an Android SDK installation.\n' +
+                        'Try reinstall Android SDK or update your PATH to 
include valid path to SDK');
                 }
             }
         }
@@ -240,7 +241,10 @@ module.exports.check_android = function () {
             process.env.PATH += path.delimiter + 
path.join(process.env.ANDROID_HOME, 'platform-tools');
         }
         if (hasAndroidHome && !avdmanagerInPath) {
-            process.env.PATH += path.delimiter + 
path.join(process.env.ANDROID_HOME, 'tools', 'bin');
+            const cmdLineToolsBin = AndroidCommandLineTools.getBinPath();
+            if (cmdLineToolsBin) {
+                process.env.PATH += path.delimiter + cmdLineToolsBin;
+            }
         }
         return hasAndroidHome;
     });
diff --git a/lib/env/AndroidCommandLineTools.js 
b/lib/env/AndroidCommandLineTools.js
new file mode 100644
index 00000000..e99ccc3c
--- /dev/null
+++ b/lib/env/AndroidCommandLineTools.js
@@ -0,0 +1,102 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+const { events } = require('cordova-common');
+const fs = require('node:fs');
+const path = require('node:path');
+const semver = require('semver');
+
+/**
+ * Utility collection for resolving the Android SDK command line tools 
installed
+ * on the workstation.
+ */
+const AndroidCommandLineTools = {
+    /**
+     * Gets a sorted list of available versions found on the system.
+     *
+     * If the command line tools is not resolvable, then an empty array will 
be returned.
+     *
+     * This function depends on ANDROID_HOME environment variable.
+     *
+     * @returns {String[]}
+     */
+    getAvailableVersions: () => {
+        const androidHome = 
path.resolve(AndroidCommandLineTools.__getAndroidHome());
+
+        if (!fs.existsSync(androidHome)) {
+            events.emit('warn', 'ANDROID_HOME is not resolvable.');
+            return [];
+        }
+
+        const cmdLineToolsContainer = path.join(androidHome, 'cmdline-tools');
+        if (!fs.existsSync(cmdLineToolsContainer)) {
+            events.emit('warn', 'Android SDK is missing cmdline-tools 
directory.');
+            return [];
+        }
+
+        const cmdLineVersions = fs.readdirSync(cmdLineToolsContainer)
+            .filter((value) => {
+                // expected directory paths are semver-like version strings or 
literally "latest"
+                return value === 'latest' || semver.coerce(value) !== null;
+            })
+            .sort((a, b) => {
+                // "latest" directory always comes first
+                if (a === 'latest') return -1;
+                if (b === 'latest') return 1;
+
+                const av = semver.coerce(a, {
+                    includePrerelease: true
+                });
+                const bv = semver.coerce(b, {
+                    includePrerelease: true
+                });
+
+                // Descending (highest version first)
+                return semver.rcompare(av, bv);
+            });
+
+        return cmdLineVersions;
+    },
+
+    /**
+     * Gets the bin path of the cmd line tools using the latest available that
+     * is installed on the workstation.
+     *
+     * Returns null if there are no versions fond
+     *
+     * @returns {String | null}
+     */
+    getBinPath: () => {
+        const versions = AndroidCommandLineTools.getAvailableVersions();
+
+        if (versions.length === 0) {
+            return null;
+        }
+
+        const version = versions[0];
+        return path.resolve(AndroidCommandLineTools.__getAndroidHome(), 
'cmdline-tools', version, 'bin');
+    },
+
+    /**
+     * @internal
+     */
+    __getAndroidHome: () => process.env.ANDROID_HOME
+};
+
+module.exports = AndroidCommandLineTools;
diff --git a/spec/unit/AndroidCommandLineTools.spec.js 
b/spec/unit/AndroidCommandLineTools.spec.js
new file mode 100644
index 00000000..dc7f1fab
--- /dev/null
+++ b/spec/unit/AndroidCommandLineTools.spec.js
@@ -0,0 +1,105 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+const fs = require('node:fs');
+const path = require('node:path');
+const AndroidCommandLineTools = 
require('../../lib/env/AndroidCommandLineTools');
+
+describe('AndroidCommandLineTools', () => {
+    beforeAll(() => {
+        // For the purposes of these test, we will assume ANDROID_HOME is 
proper.
+        spyOn(fs, 'existsSync').and.returnValue(true);
+    });
+
+    describe('getAvailableVersions', () => {
+        describe('should return a list of command line versions', () => {
+            it('in descending order', () => {
+                spyOn(fs, 'readdirSync').and.returnValue([
+                    '10.0',
+                    '15.0',
+                    '13'
+                ]);
+
+                
expect(AndroidCommandLineTools.getAvailableVersions()).toEqual([
+                    '15.0',
+                    '13',
+                    '10.0'
+                ]);
+            });
+
+            it('stable releases appear before prereleases', () => {
+                spyOn(fs, 'readdirSync').and.returnValue([
+                    '15.0-rc01',
+                    '15.0-alpha01',
+                    '15.0'
+                ]);
+
+                
expect(AndroidCommandLineTools.getAvailableVersions()).toEqual([
+                    '15.0',
+                    '15.0-rc01',
+                    '15.0-alpha01'
+                ]);
+            });
+
+            it('"latest" should take all precedence', () => {
+                spyOn(fs, 'readdirSync').and.returnValue([
+                    '15.0-rc01',
+                    '15.0-alpha01',
+                    '15.0',
+                    'latest'
+                ]);
+
+                
expect(AndroidCommandLineTools.getAvailableVersions()).toEqual([
+                    'latest',
+                    '15.0',
+                    '15.0-rc01',
+                    '15.0-alpha01'
+                ]);
+            });
+
+            it('invalid versions are ignored', () => {
+                spyOn(fs, 'readdirSync').and.returnValue([
+                    '15.0-rc01',
+                    'xyz',
+                    '15.0'
+                ]);
+
+                
expect(AndroidCommandLineTools.getAvailableVersions()).toEqual([
+                    '15.0',
+                    '15.0-rc01'
+                ]);
+            });
+        });
+    });
+
+    describe('getBinPath', () => {
+        beforeEach(() => {
+            spyOn(AndroidCommandLineTools, 
'__getAndroidHome').and.returnValue('/Android/Sdk');
+        });
+
+        it('should return the bin path of the latest version', () => {
+            spyOn(AndroidCommandLineTools, 
'getAvailableVersions').and.returnValue([
+                '19.0',
+                '18.0'
+            ]);
+
+            
expect(AndroidCommandLineTools.getBinPath()).toBe(path.resolve('/Android/Sdk/cmdline-tools/19.0/bin'));
+        });
+    });
+});
diff --git a/spec/unit/check_reqs.spec.js b/spec/unit/check_reqs.spec.js
index 54df1cf7..4628f41a 100644
--- a/spec/unit/check_reqs.spec.js
+++ b/spec/unit/check_reqs.spec.js
@@ -27,6 +27,7 @@ const which = require('which');
 const {
     SDK_VERSION: DEFAULT_TARGET_API
 } = require('../../lib/gradle-config-defaults');
+const AndroidCommandLineTools = 
require('../../lib/env/AndroidCommandLineTools');
 
 describe('check_reqs', function () {
     let check_reqs;
@@ -58,6 +59,10 @@ describe('check_reqs', function () {
     });
 
     describe('check_android', function () {
+        beforeAll(() => {
+            spyOn(AndroidCommandLineTools, 
'getAvailableVersions').and.returnValue(['latest']);
+        });
+
         describe('find and set ANDROID_HOME when neither ANDROID_HOME nor 
ANDROID_SDK_ROOT is set', function () {
             beforeEach(function () {
                 delete process.env.ANDROID_HOME;
@@ -123,13 +128,13 @@ describe('check_reqs', function () {
                     spyOn(fs, 'existsSync').and.returnValue(true);
                     spyOn(which, 'sync').and.callFake(function (cmd) {
                         if (cmd === 'avdmanager') {
-                            return 
path.normalize('/android/sdk/tools/bin/avdmanager');
+                            return 
path.resolve('/android/sdk/cmdline-tools/latest/bin/avdmanager');
                         } else {
                             return null;
                         }
                     });
                     return check_reqs.check_android().then(function () {
-                        
expect(process.env.ANDROID_HOME).toEqual(path.normalize('/android/sdk'));
+                        
expect(process.env.ANDROID_HOME).toEqual(path.resolve('/android/sdk'));
                     });
                 });
                 it('should error out if `avdmanager` command exists in a 
non-SDK-like directory structure', () => {
@@ -208,9 +213,8 @@ describe('check_reqs', function () {
             });
             it('should add tools/bin,tools,platform-tools to PATH if 
`avdmanager`,`android`,`adb` is not found', () => {
                 return check_reqs.check_android().then(function () {
-                    expect(process.env.PATH).toContain('let the children play' 
+ path.sep + 'tools');
                     expect(process.env.PATH).toContain('let the children play' 
+ path.sep + 'platform-tools');
-                    expect(process.env.PATH).toContain('let the children play' 
+ path.sep + 'tools' + path.sep + 'bin');
+                    expect(process.env.PATH).toContain('let the children play' 
+ path.sep + 'cmdline-tools' + path.sep + 'latest' + path.sep + 'bin');
                 });
             });
         });


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to