@@ -4,12 +4,43 @@ import { createHash } from 'node:crypto';
44import { tailgate } from '../util/tailgate.js' ;
55import snooplogg from 'snooplogg' ;
66import { isDir } from '../util/is-dir.js' ;
7+ import { join } from 'node:path' ;
8+ import { readPropertiesFile } from './util/read-properties-file.js' ;
9+ import { isFile } from '../util/is-file.js' ;
10+ import { readdir } from 'node:fs/promises' ;
711
812const { error, log } = snooplogg ( 'android:sdk' ) ;
913
14+ const bat = process . platform === 'win32' ? '.bat' : '' ;
15+ const exe = process . platform === 'win32' ? '.exe' : '' ;
16+
17+ interface AndroidSDKExecutables {
18+ adb : string ;
19+ android : string ;
20+ emulator : string ;
21+ }
22+
1023export class AndroidSDK {
11- constructor ( ) {
12- //
24+ buildTools : Record < string , string > ;
25+ executables : AndroidSDKExecutables ;
26+ path : string ;
27+ version : string ;
28+
29+ constructor ( {
30+ buildTools,
31+ executables,
32+ path,
33+ version
34+ } : {
35+ buildTools : Record < string , string > ;
36+ executables : AndroidSDKExecutables ;
37+ path : string ;
38+ version : string ;
39+ } ) {
40+ this . buildTools = buildTools ;
41+ this . executables = executables ;
42+ this . path = path ;
43+ this . version = version ;
1344 }
1445
1546 static async load ( path : string ) : Promise < AndroidSDK > {
@@ -21,7 +52,192 @@ export class AndroidSDK {
2152 throw new Error ( 'Android SDK path does not exist: ${path}' ) ;
2253 }
2354
24- return new AndroidSDK ( ) ;
55+ const executables = {
56+ adb : join ( path , 'platform-tools' , `adb${ exe } ` ) ,
57+ android : join ( path , 'tools' , `android${ bat } ` ) ,
58+ emulator : join ( path , 'tools' , `emulator${ exe } ` ) ,
59+ } ;
60+
61+ const buildTools : Record < string , string > = { } ;
62+ for ( const ver of await readdir ( join ( path , 'build-tools' ) ) ) {
63+ const dir = join ( path , 'build-tools' , ver ) ;
64+ if ( isFile ( join ( dir , 'source.properties' ) ) ) {
65+ buildTools [ ver ] = dir ;
66+ }
67+ }
68+
69+ const systemImagesDir = join ( path , 'system-images' ) ;
70+
71+ // /**
72+ // * Detect system images
73+ // */
74+ // const systemImagesDir = path.join(dir, 'system-images');
75+ // if (isDir(systemImagesDir)) {
76+ // for (const platform of fs.readdirSync(systemImagesDir)) {
77+ // const platformDir = path.join(systemImagesDir, platform);
78+ // if (isDir(platformDir)) {
79+ // for (const tag of fs.readdirSync(platformDir)) {
80+ // const tagDir = path.join(platformDir, tag);
81+ // if (isDir(tagDir)) {
82+ // for (const abi of fs.readdirSync(tagDir)) {
83+ // const abiDir = path.join(tagDir, abi);
84+ // const props = readPropertiesFile(path.join(abiDir, 'source.properties'));
85+ // if (props && props['AndroidVersion.ApiLevel'] && props['SystemImage.TagId'] && props['SystemImage.Abi']) {
86+ // const imageDir = path.relative(systemImagesDir, abiDir).replace(/\\/g, '/');
87+ // const skinsDir = path.join(abiDir, 'skins');
88+
89+ // this.systemImages[imageDir] = {
90+ // abi: props['SystemImage.Abi'],
91+ // sdk: `android-${props['AndroidVersion.CodeName'] || props['AndroidVersion.ApiLevel']}`,
92+ // skins: isDir(skinsDir) ? fs.readdirSync(skinsDir).map(name => {
93+ // return isFile(path.join(skinsDir, name, 'hardware.ini')) ? name : null;
94+ // }).filter(x => x) : [],
95+ // type: props['SystemImage.TagId']
96+ // };
97+ // }
98+ // }
99+ // }
100+ // }
101+ // }
102+ // }
103+ // }
104+
105+ // /**
106+ // * Detect platforms
107+ // */
108+ // const platformsDir = path.join(dir, 'platforms');
109+ // if (isDir(platformsDir)) {
110+ // for (const name of fs.readdirSync(platformsDir)) {
111+ // const dir = path.join(platformsDir, name);
112+ // const sourceProps = readPropertiesFile(path.join(dir, 'source.properties'));
113+ // const apiLevel = sourceProps ? ~~sourceProps['AndroidVersion.ApiLevel'] : null;
114+ // if (!sourceProps || !apiLevel || !isFile(path.join(dir, 'build.prop'))) {
115+ // continue;
116+ // }
117+
118+ // // read in the sdk properties, if exists
119+ // const sdkProps = readPropertiesFile(path.join(dir, 'sdk.properties'));
120+
121+ // // detect the available skins
122+ // const skinsDir = path.join(dir, 'skins');
123+ // const skins = isDir(skinsDir) ? fs.readdirSync(skinsDir).map(name => {
124+ // return isFile(path.join(skinsDir, name, 'hardware.ini')) ? name : null;
125+ // }).filter(x => x) : [];
126+ // let defaultSkin = sdkProps && sdkProps['sdk.skin.default'];
127+ // if (skins.indexOf(defaultSkin) === -1 && skins.indexOf(defaultSkin = 'WVGA800') === -1) {
128+ // defaultSkin = skins[skins.length - 1] || null;
129+ // }
130+
131+ // const apiName = sourceProps['AndroidVersion.CodeName'] || apiLevel;
132+ // const sdk = `android-${apiName}`;
133+ // let tmp;
134+
135+ // const abis = {};
136+ // for (const image of Object.values(this.systemImages)) {
137+ // if (image.sdk === sdk) {
138+ // if (!abis[image.type]) {
139+ // abis[image.type] = [];
140+ // }
141+ // if (!abis[image.type].includes(image.abi)) {
142+ // abis[image.type].push(image.abi);
143+ // }
144+
145+ // for (const skin of image.skins) {
146+ // if (!skins.includes(skin)) {
147+ // skins.push(skin);
148+ // }
149+ // }
150+ // }
151+ // }
152+
153+ // this.platforms.push({
154+ // abis: abis,
155+ // aidl: isFile(tmp = path.join(dir, 'framework.aidl')) ? tmp : null,
156+ // androidJar: isFile(tmp = path.join(dir, 'android.jar')) ? tmp : null,
157+ // apiLevel: apiLevel,
158+ // codename: sourceProps['AndroidVersion.CodeName'] || null,
159+ // defaultSkin: defaultSkin,
160+ // minToolsRev: +sourceProps['Platform.MinToolsRev'] || null,
161+ // name: `Android ${sourceProps['Platform.Version']}${sourceProps['AndroidVersion.CodeName'] ? ' (Preview)' : ''}`,
162+ // path: dir,
163+ // revision: +sourceProps['Layoutlib.Revision'] || null,
164+ // sdk,
165+ // skins: skins,
166+ // version: sourceProps['Platform.Version']
167+ // });
168+ // }
169+ // }
170+
171+ // /**
172+ // * Detect addons
173+ // */
174+ // const addonsDir = path.join(dir, 'add-ons');
175+ // if (isDir(addonsDir)) {
176+ // for (const name of fs.readdirSync(addonsDir)) {
177+ // const dir = path.join(addonsDir, name);
178+ // const props = readPropertiesFile(path.join(dir, 'source.properties')) || readPropertiesFile(path.join(dir, 'manifest.ini'));
179+ // if (!props) {
180+ // continue;
181+ // }
182+
183+ // const apiLevel = parseInt(props['AndroidVersion.ApiLevel'] || props.api);
184+ // const vendorDisplay = props['Addon.VendorDisplay'] || props.vendor;
185+ // const nameDisplay = props['Addon.NameDisplay'] || props.name;
186+ // if (!apiLevel || isNaN(apiLevel) || !vendorDisplay || !nameDisplay) {
187+ // continue;
188+ // }
189+
190+ // let basedOn = null;
191+ // for (const platform of this.platforms) {
192+ // if (platform.codename === null && platform.apiLevel === apiLevel) {
193+ // basedOn = platform;
194+ // break;
195+ // }
196+ // }
197+
198+ // this.addons.push({
199+ // abis: basedOn && basedOn.abis || null,
200+ // aidl: basedOn && basedOn.aidl || null,
201+ // androidJar: basedOn && basedOn.androidJar || null,
202+ // apiLevel: apiLevel,
203+ // basedOn: basedOn ? { version: basedOn.version, apiLevel: basedOn.apiLevel } : null,
204+ // codename: props['AndroidVersion.CodeName'] || props.codename || null,
205+ // defaultSkin: basedOn && basedOn.defaultSkin || null,
206+ // description: props['Pkg.Desc'] || props.description || null,
207+ // minToolsRev: basedOn && basedOn.minToolsRev || null,
208+ // name: nameDisplay,
209+ // path: dir,
210+ // revision: parseInt(props['Pkg.Revision'] || props.revision) || null,
211+ // sdk: `${vendorDisplay}:${nameDisplay}:${apiLevel}`,
212+ // skins: basedOn && basedOn.skins || null,
213+ // vendor: vendorDisplay,
214+ // version: basedOn && basedOn.version || null
215+ // });
216+ // }
217+ // }
218+
219+ // function sortFn(a, b) {
220+ // if (a.codename === null) {
221+ // if (b.codename !== null && a.apiLevel === b.apiLevel) {
222+ // // sort GA releases before preview releases
223+ // return -1;
224+ // }
225+ // } else if (a.apiLevel === b.apiLevel) {
226+ // return b.codename === null ? 1 : a.codename.localeCompare(b.codename);
227+ // }
228+
229+ // return a.apiLevel - b.apiLevel;
230+ // }
231+
232+ // this.platforms.sort(sortFn);
233+ // this.addons.sort(sortFn);
234+
235+ return new AndroidSDK ( {
236+ buildTools,
237+ executables,
238+ path,
239+ version : ''
240+ } ) ;
25241 }
26242}
27243
@@ -45,6 +261,17 @@ export async function detect(options: {
45261 } ) ;
46262}
47263
264+ function findExecutables < T extends Record < string , string > > ( dir : string , exes : T ) : Record < keyof T , string > {
265+ const executables : Record < string , string > = { } ;
266+ for ( const [ name , exe ] of Object . entries ( exes ) ) {
267+ const p = join ( dir , exe ) ;
268+ if ( isFile ( p ) ) {
269+ executables [ name ] = p ;
270+ }
271+ }
272+ return executables as Record < keyof T , string > ;
273+ }
274+
48275async function getSearchPaths ( options : { searchPaths ?: string [ ] } ) {
49276 const paths : string [ ] = [ ] ;
50277 if ( Array . isArray ( options . searchPaths ) ) {
0 commit comments