|
@@ -0,0 +1,209 @@
|
|
|
|
+// 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. 解压
|
|
|
|
+ extractZip(TEMP_ZIP, CMDLINE_TOOLS_DIR);
|
|
|
|
+
|
|
|
|
+ // 3. 设置环境变量
|
|
|
|
+ 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();
|