- 安卓构建成功
This commit is contained in:
8
app.json
8
app.json
@@ -41,6 +41,14 @@
|
||||
],
|
||||
"ios": {
|
||||
"bundleIdentifier": "com.hixcc.duooomi-ble-sdk"
|
||||
},
|
||||
"android": {
|
||||
"permissions": [
|
||||
"android.permission.BLUETOOTH",
|
||||
"android.permission.BLUETOOTH_ADMIN",
|
||||
"android.permission.BLUETOOTH_CONNECT"
|
||||
],
|
||||
"package": "com.hixcc.duooomiblesdk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"devDependencies": {
|
||||
"@react-native-community/cli": "^20.1.3",
|
||||
"@types/react": "~19.2.2",
|
||||
"hermes-compiler": "0.14.1",
|
||||
"typescript": "~5.9.2"
|
||||
},
|
||||
"private": true
|
||||
|
||||
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
@@ -45,6 +45,9 @@ importers:
|
||||
'@types/react':
|
||||
specifier: ~19.2.2
|
||||
version: 19.2.14
|
||||
hermes-compiler:
|
||||
specifier: 0.14.1
|
||||
version: 0.14.1
|
||||
typescript:
|
||||
specifier: ~5.9.2
|
||||
version: 5.9.3
|
||||
@@ -2322,28 +2325,24 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
|
||||
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import net from 'node:net'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
@@ -10,23 +11,368 @@ const distDir = path.join(rootDir, 'dist', 'android')
|
||||
const mavenDir = path.join(distDir, 'maven')
|
||||
const wrapperDir = path.join(distDir, 'wrapper')
|
||||
const wrapperTemplateDir = path.join(rootDir, 'wrappers', 'android')
|
||||
const androidProjectDir = path.join(rootDir, 'android')
|
||||
const gradleWrapperPropertiesPath = path.join(androidProjectDir, 'gradle', 'wrapper', 'gradle-wrapper.properties')
|
||||
const androidBuildGradlePath = path.join(androidProjectDir, 'build.gradle')
|
||||
const androidLocalPropertiesPath = path.join(androidProjectDir, 'local.properties')
|
||||
const gradleWrapper = process.platform === 'win32' ? 'gradlew.bat' : './gradlew'
|
||||
const gradleTask = 'publishBrownfieldReleasePublicationTodistRepository'
|
||||
let commandRootDir = rootDir
|
||||
let commandAndroidProjectDir = androidProjectDir
|
||||
const commonProxyCandidates = [
|
||||
{ protocol: 'http', host: '127.0.0.1', port: 7890 },
|
||||
{ protocol: 'http', host: '127.0.0.1', port: 7897 },
|
||||
{ protocol: 'http', host: '127.0.0.1', port: 8080 },
|
||||
{ protocol: 'http', host: '127.0.0.1', port: 10809 },
|
||||
{ protocol: 'socks', host: '127.0.0.1', port: 7891 },
|
||||
{ protocol: 'socks', host: '127.0.0.1', port: 10808 },
|
||||
]
|
||||
|
||||
function run(command, args) {
|
||||
const executable = process.platform === 'win32' ? `${command}.cmd` : command
|
||||
const result = spawnSync(executable, args, {
|
||||
cwd: rootDir,
|
||||
function run(command, args, env = process.env, cwd = commandRootDir) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd,
|
||||
env,
|
||||
shell: process.platform === 'win32',
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
if (result.error) {
|
||||
throw result.error
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1)
|
||||
}
|
||||
}
|
||||
|
||||
function ensureAndroidProject() {
|
||||
if (existsSync(androidProjectDir)) {
|
||||
return
|
||||
}
|
||||
|
||||
run('pnpx', ['expo', 'prebuild', '--platform', 'android', '--pnpm'], buildEnv)
|
||||
}
|
||||
|
||||
function ensureGradleWrapperTimeout() {
|
||||
if (!existsSync(gradleWrapperPropertiesPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
const properties = readFileSync(gradleWrapperPropertiesPath, 'utf8')
|
||||
|
||||
if (properties.includes('networkTimeout=120000')) {
|
||||
return
|
||||
}
|
||||
|
||||
const updatedProperties = properties.includes('networkTimeout=')
|
||||
? properties.replace(/networkTimeout=\d+/g, 'networkTimeout=120000')
|
||||
: `${properties.trimEnd()}\nnetworkTimeout=120000\n`
|
||||
|
||||
writeFileSync(gradleWrapperPropertiesPath, updatedProperties)
|
||||
}
|
||||
|
||||
function ensureAndroidPublishPath() {
|
||||
if (!existsSync(androidBuildGradlePath)) {
|
||||
return
|
||||
}
|
||||
|
||||
const buildGradle = readFileSync(androidBuildGradlePath, 'utf8')
|
||||
const expectedUrl = pathToFileURL(mavenDir).toString()
|
||||
const updatedBuildGradle = buildGradle.replace(
|
||||
/url\.set\("file:\/\/[^"]*"\)/g,
|
||||
`url.set("${expectedUrl}")`
|
||||
)
|
||||
|
||||
if (updatedBuildGradle !== buildGradle) {
|
||||
writeFileSync(androidBuildGradlePath, updatedBuildGradle)
|
||||
}
|
||||
}
|
||||
|
||||
function escapePropertiesPath(value) {
|
||||
return value.replace(/\\/g, '\\\\')
|
||||
}
|
||||
|
||||
function resolveAndroidSdkPath() {
|
||||
const candidates = [
|
||||
process.env.ANDROID_HOME,
|
||||
process.env.ANDROID_SDK_ROOT,
|
||||
path.join(process.env.LOCALAPPDATA || '', 'Android', 'Sdk'),
|
||||
path.join(process.env.USERPROFILE || '', 'AppData', 'Local', 'Android', 'Sdk'),
|
||||
].filter(Boolean)
|
||||
|
||||
return candidates.find((candidate) => existsSync(candidate)) || null
|
||||
}
|
||||
|
||||
function resolveAndroidSdkCmakeDir() {
|
||||
const sdkPath = resolveAndroidSdkPath()
|
||||
if (!sdkPath) {
|
||||
return null
|
||||
}
|
||||
|
||||
const preferredVersionDir = path.join(sdkPath, 'cmake', '4.1.2')
|
||||
if (existsSync(preferredVersionDir)) {
|
||||
return preferredVersionDir
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function ensureAndroidLocalProperties() {
|
||||
const sdkPath = resolveAndroidSdkPath()
|
||||
const cmakeDir = resolveAndroidSdkCmakeDir()
|
||||
|
||||
if (!sdkPath && !cmakeDir) {
|
||||
return
|
||||
}
|
||||
|
||||
const lines = []
|
||||
|
||||
if (sdkPath) {
|
||||
lines.push(`sdk.dir=${escapePropertiesPath(sdkPath)}`)
|
||||
}
|
||||
|
||||
if (cmakeDir) {
|
||||
lines.push(`cmake.dir=${escapePropertiesPath(cmakeDir)}`)
|
||||
}
|
||||
|
||||
const content = `${lines.join('\n')}\n`
|
||||
writeFileSync(androidLocalPropertiesPath, content)
|
||||
}
|
||||
|
||||
function normalizeProxyUrl(rawValue) {
|
||||
if (!rawValue) {
|
||||
return null
|
||||
}
|
||||
|
||||
const normalized = rawValue.includes('://') ? rawValue : `http://${rawValue}`
|
||||
|
||||
try {
|
||||
const url = new URL(normalized)
|
||||
return {
|
||||
protocol: url.protocol.replace(':', ''),
|
||||
host: url.hostname,
|
||||
port: Number(url.port || (url.protocol === 'https:' ? '443' : '80')),
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function runAndCapture(command, args) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: commandRootDir,
|
||||
shell: process.platform === 'win32',
|
||||
encoding: 'utf8',
|
||||
})
|
||||
|
||||
if (result.error || result.status !== 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (result.stdout || '').trim()
|
||||
}
|
||||
|
||||
function parseWindowsProxyServer(proxyServer) {
|
||||
if (!proxyServer) {
|
||||
return null
|
||||
}
|
||||
|
||||
const normalized = proxyServer.trim()
|
||||
const segments = normalized.split(';').map((segment) => segment.trim()).filter(Boolean)
|
||||
|
||||
if (segments.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const preferredSegment =
|
||||
segments.find((segment) => segment.startsWith('https=')) ||
|
||||
segments.find((segment) => segment.startsWith('http=')) ||
|
||||
segments.find((segment) => segment.startsWith('socks=')) ||
|
||||
segments[0]
|
||||
|
||||
const [scheme, value] = preferredSegment.includes('=')
|
||||
? preferredSegment.split('=', 2)
|
||||
: ['http', preferredSegment]
|
||||
|
||||
return normalizeProxyUrl(`${scheme}://${value}`)
|
||||
}
|
||||
|
||||
function resolveWindowsSystemProxy() {
|
||||
if (process.platform !== 'win32') {
|
||||
return null
|
||||
}
|
||||
|
||||
const registryJson = runAndCapture('powershell', [
|
||||
'-NoProfile',
|
||||
'-Command',
|
||||
"$settings = Get-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'; [PSCustomObject]@{ enabled = [bool]$settings.ProxyEnable; server = $settings.ProxyServer } | ConvertTo-Json -Compress",
|
||||
])
|
||||
|
||||
if (registryJson) {
|
||||
try {
|
||||
const registrySettings = JSON.parse(registryJson)
|
||||
if (registrySettings?.enabled && registrySettings?.server) {
|
||||
const proxy = parseWindowsProxyServer(registrySettings.server)
|
||||
if (proxy) {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore parsing errors and fall through to the next strategy.
|
||||
}
|
||||
}
|
||||
|
||||
const winHttpOutput = runAndCapture('netsh', ['winhttp', 'show', 'proxy'])
|
||||
if (!winHttpOutput) {
|
||||
return null
|
||||
}
|
||||
|
||||
const proxyLine = winHttpOutput
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.find((line) => line.toLowerCase().startsWith('proxy server'))
|
||||
|
||||
if (!proxyLine) {
|
||||
return null
|
||||
}
|
||||
|
||||
const server = proxyLine.split(':').slice(1).join(':').trim()
|
||||
return parseWindowsProxyServer(server)
|
||||
}
|
||||
|
||||
function canConnect({ host, port }) {
|
||||
return new Promise((resolve) => {
|
||||
const socket = new net.Socket()
|
||||
const done = (value) => {
|
||||
socket.destroy()
|
||||
resolve(value)
|
||||
}
|
||||
|
||||
socket.setTimeout(300)
|
||||
socket.once('connect', () => done(true))
|
||||
socket.once('timeout', () => done(false))
|
||||
socket.once('error', () => done(false))
|
||||
socket.connect(port, host)
|
||||
})
|
||||
}
|
||||
|
||||
async function resolveProxy() {
|
||||
const envProxy = normalizeProxyUrl(
|
||||
process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy
|
||||
)
|
||||
|
||||
if (envProxy) {
|
||||
return envProxy
|
||||
}
|
||||
|
||||
const systemProxy = resolveWindowsSystemProxy()
|
||||
if (systemProxy) {
|
||||
return systemProxy
|
||||
}
|
||||
|
||||
for (const candidate of commonProxyCandidates) {
|
||||
if (await canConnect(candidate)) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function buildJavaToolOptions(proxy) {
|
||||
const current = process.env.JAVA_TOOL_OPTIONS?.trim()
|
||||
if (!proxy) {
|
||||
return current || undefined
|
||||
}
|
||||
|
||||
const proxyArgs = proxy.protocol === 'socks'
|
||||
? [
|
||||
`-DsocksProxyHost=${proxy.host}`,
|
||||
`-DsocksProxyPort=${proxy.port}`,
|
||||
]
|
||||
: [
|
||||
`-Dhttp.proxyHost=${proxy.host}`,
|
||||
`-Dhttp.proxyPort=${proxy.port}`,
|
||||
`-Dhttps.proxyHost=${proxy.host}`,
|
||||
`-Dhttps.proxyPort=${proxy.port}`,
|
||||
]
|
||||
|
||||
return [current, ...proxyArgs].filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
function buildCommandEnv(proxy) {
|
||||
const env = { ...process.env }
|
||||
const javaToolOptions = buildJavaToolOptions(proxy)
|
||||
|
||||
if (javaToolOptions) {
|
||||
env.JAVA_TOOL_OPTIONS = javaToolOptions
|
||||
}
|
||||
|
||||
if (proxy && proxy.protocol !== 'socks') {
|
||||
const proxyUrl = `http://${proxy.host}:${proxy.port}`
|
||||
env.HTTP_PROXY = env.HTTP_PROXY || env.http_proxy || proxyUrl
|
||||
env.HTTPS_PROXY = env.HTTPS_PROXY || env.https_proxy || proxyUrl
|
||||
env.http_proxy = env.http_proxy || env.HTTP_PROXY
|
||||
env.https_proxy = env.https_proxy || env.HTTPS_PROXY
|
||||
}
|
||||
|
||||
env.NODE_ENV = env.NODE_ENV || 'production'
|
||||
env.ORG_GRADLE_PROJECT_newArchEnabled = 'false'
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
function ensureWindowsShortPathWorkspace() {
|
||||
if (process.platform !== 'win32') {
|
||||
return
|
||||
}
|
||||
|
||||
const currentDrive = path.parse(rootDir).root.replace(/\\/g, '').replace(':', '').toUpperCase()
|
||||
const candidateDrives = ['X', 'Y', 'Z', 'W', 'V']
|
||||
const driveLetter = candidateDrives.find((candidate) => candidate !== currentDrive)
|
||||
|
||||
if (!driveLetter) {
|
||||
return
|
||||
}
|
||||
|
||||
const mappedRoot = `${driveLetter}:\\`
|
||||
const substList = runAndCapture('subst', []) || ''
|
||||
|
||||
if (!substList.includes(`${driveLetter}: =>`)) {
|
||||
const result = spawnSync('subst', [`${driveLetter}:`, rootDir], {
|
||||
cwd: rootDir,
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
if (result.error || result.status !== 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
commandRootDir = mappedRoot
|
||||
commandAndroidProjectDir = path.join(mappedRoot, 'android')
|
||||
console.log(`Using short workspace path: ${mappedRoot}`)
|
||||
}
|
||||
|
||||
const proxy = await resolveProxy()
|
||||
const buildEnv = buildCommandEnv(proxy)
|
||||
|
||||
if (proxy) {
|
||||
console.log(`Using local ${proxy.protocol.toUpperCase()} proxy: ${proxy.host}:${proxy.port}`)
|
||||
} else {
|
||||
console.log('No local proxy detected, using direct network access')
|
||||
}
|
||||
|
||||
rmSync(distDir, { recursive: true, force: true })
|
||||
mkdirSync(distDir, { recursive: true })
|
||||
|
||||
run('pnpx', ['expo-brownfield', 'build:android', '--release'])
|
||||
ensureWindowsShortPathWorkspace()
|
||||
ensureAndroidProject()
|
||||
ensureGradleWrapperTimeout()
|
||||
ensureAndroidPublishPath()
|
||||
ensureAndroidLocalProperties()
|
||||
run(gradleWrapper, [gradleTask], buildEnv, commandAndroidProjectDir)
|
||||
|
||||
if (!existsSync(mavenDir)) {
|
||||
throw new Error(`Missing Android Maven output: ${mavenDir}`)
|
||||
|
||||
@@ -13,12 +13,16 @@ const frameworksDir = path.join(packageDir, 'Frameworks')
|
||||
const wrapperTemplateDir = path.join(rootDir, 'wrappers', 'ios')
|
||||
|
||||
function run(command, args) {
|
||||
const executable = process.platform === 'win32' ? `${command}.cmd` : command
|
||||
const result = spawnSync(executable, args, {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: rootDir,
|
||||
shell: process.platform === 'win32',
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
if (result.error) {
|
||||
throw result.error
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user