// setup-android-sdk.js - 安装和配置 Android SDK const fs = require('fs'); const path = require('path'); const https = require('https'); const http = require('http'); const { execSync } = require('child_process'); const { URL } = require('url'); // 配置 const SDK_URL = 'https://dl.google.com/android/repository/commandlinetools-win-11076708_latest.zip'; const SDK_ROOT = 'C:\\android-sdk'; const CMDLINE_TOOLS_DIR = path.join(SDK_ROOT, 'cmdline-tools', 'latest'); const TEMP_ZIP = path.join(process.env.TEMP || '/tmp', 'cmdtools.zip'); // 从环境变量获取代理设置 const PROXY = process.env.HTTPS_PROXY || process.env.HTTP_PROXY; console.log('🚀 开始安装 Android SDK...'); console.log(`📦 SDK Root: ${SDK_ROOT}`); if (PROXY) { console.log(`🌐 使用代理: ${PROXY}`); } // 下载文件(支持代理) function downloadFile(url, dest) { return new Promise((resolve, reject) => { console.log(`📥 下载: ${url}`); const file = fs.createWriteStream(dest); const urlObj = new URL(url); let requestLib = https; let options = { hostname: urlObj.hostname, path: urlObj.pathname + urlObj.search, method: 'GET', headers: { 'User-Agent': 'Node.js' } }; // 如果有代理,使用代理设置 if (PROXY) { const proxyUrl = new URL(PROXY); requestLib = proxyUrl.protocol === 'https:' ? https : http; options = { hostname: proxyUrl.hostname, port: proxyUrl.port, path: url, // 使用完整 URL 作为路径 method: 'GET', headers: { 'Host': urlObj.hostname, 'User-Agent': 'Node.js' } }; } const request = requestLib.get(options, (response) => { // 处理重定向 if (response.statusCode === 301 || response.statusCode === 302) { const redirectUrl = response.headers.location; console.log(`🔀 重定向到: ${redirectUrl}`); file.close(); fs.unlinkSync(dest); return downloadFile(redirectUrl, dest).then(resolve).catch(reject); } if (response.statusCode !== 200) { reject(new Error(`下载失败,状态码: ${response.statusCode}`)); return; } const totalSize = parseInt(response.headers['content-length'], 10); let downloadedSize = 0; response.on('data', (chunk) => { downloadedSize += chunk.length; const percent = ((downloadedSize / totalSize) * 100).toFixed(2); process.stdout.write(`\r下载进度: ${percent}% (${downloadedSize}/${totalSize} bytes)`); }); response.pipe(file); file.on('finish', () => { file.close(); console.log('\n✅ 下载完成'); resolve(); }); }); request.on('error', (err) => { fs.unlinkSync(dest); reject(err); }); file.on('error', (err) => { fs.unlinkSync(dest); reject(err); }); }); } // 解压 ZIP 文件 function extractZip(zipPath, destDir) { console.log(`📂 解压到: ${destDir}`); // 确保目标目录存在 fs.mkdirSync(destDir, { recursive: true }); // 在 Windows 上使用 PowerShell 的 Expand-Archive if (process.platform === 'win32') { try { const cmd = `powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`; execSync(cmd, { stdio: 'inherit' }); console.log('✅ 解压完成'); } catch (error) { throw new Error(`解压失败: ${error.message}`); } } else { // Linux 使用 unzip try { execSync(`unzip -q "${zipPath}" -d "${destDir}"`, { stdio: 'inherit' }); console.log('✅ 解压完成'); } catch (error) { throw new Error(`解压失败: ${error.message}`); } } } // 设置环境变量(写入 GITHUB_ENV) function setGitHubEnv(name, value) { const envFile = process.env.GITHUB_ENV; if (envFile) { fs.appendFileSync(envFile, `${name}=${value}\n`); console.log(`✅ 设置环境变量: ${name}=${value}`); } else { console.log(`⚠️ GITHUB_ENV 未定义,跳过设置: ${name}=${value}`); } } // 运行 sdkmanager function runSdkManager(args) { const sdkManagerPath = path.join(CMDLINE_TOOLS_DIR, 'bin', 'sdkmanager.bat'); if (!fs.existsSync(sdkManagerPath)) { throw new Error(`sdkmanager 不存在: ${sdkManagerPath}`); } console.log(`🔧 运行: sdkmanager ${args.join(' ')}`); try { // 构建完整的命令 const cmd = `"${sdkManagerPath}" ${args.map(arg => `"${arg}"`).join(' ')}`; execSync(cmd, { stdio: 'inherit', shell: 'cmd.exe' }); console.log('✅ sdkmanager 执行成功'); } catch (error) { throw new Error(`sdkmanager 执行失败: ${error.message}`); } } // 主函数 async function main() { try { // 1. 下载 SDK await downloadFile(SDK_URL, TEMP_ZIP); // 2. 解压到临时目录 const tempExtractDir = path.join(SDK_ROOT, 'cmdline-tools', 'temp'); extractZip(TEMP_ZIP, tempExtractDir); // 3. 移动内部的 cmdline-tools 到 latest(ZIP 内部包含 cmdline-tools 目录) const extractedCmdlineTools = path.join(tempExtractDir, 'cmdline-tools'); if (fs.existsSync(extractedCmdlineTools)) { console.log('📦 重新组织目录结构...'); // 如果 latest 目录已存在,先删除 if (fs.existsSync(CMDLINE_TOOLS_DIR)) { fs.rmSync(CMDLINE_TOOLS_DIR, { recursive: true, force: true }); } fs.renameSync(extractedCmdlineTools, CMDLINE_TOOLS_DIR); // 删除临时目录 fs.rmSync(tempExtractDir, { recursive: true, force: true }); console.log('✅ 目录结构已调整'); } else { throw new Error(`解压后未找到 cmdline-tools 目录: ${extractedCmdlineTools}`); } // 4. 设置环境变量 setGitHubEnv('ANDROID_HOME', SDK_ROOT); setGitHubEnv('ANDROID_SDK_ROOT', SDK_ROOT); // 4. 安装 platforms 和 build-tools runSdkManager([ '--install', 'platforms;android-35', 'build-tools;35.0.0', `--sdk_root=${SDK_ROOT}` ]); // 5. 接受 licenses(使用 echo y) console.log('📝 接受 SDK licenses...'); const sdkManagerPath = path.join(CMDLINE_TOOLS_DIR, 'bin', 'sdkmanager.bat'); const licenseCmd = `echo y | "${sdkManagerPath}" "--licenses" "--sdk_root=${SDK_ROOT}"`; execSync(licenseCmd, { stdio: 'inherit', shell: 'cmd.exe' }); console.log('🎉 Android SDK 安装完成!'); // 清理临时文件 if (fs.existsSync(TEMP_ZIP)) { fs.unlinkSync(TEMP_ZIP); console.log('🧹 已清理临时文件'); } } catch (error) { console.error('❌ 错误:', error.message); process.exit(1); } } // 运行 main();