This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
11 KiB
343 lines
11 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
*/ |
|
'use strict'; |
|
|
|
const adb = require('./adb'); |
|
const chalk = require('chalk'); |
|
const child_process = require('child_process'); |
|
const fs = require('fs'); |
|
const isPackagerRunning = require('../util/isPackagerRunning'); |
|
const findReactNativeScripts = require('../util/findReactNativeScripts'); |
|
const isString = require('lodash/isString'); |
|
const path = require('path'); |
|
const Promise = require('promise'); |
|
|
|
// Verifies this is an Android project |
|
function checkAndroid(root) { |
|
return fs.existsSync(path.join(root, 'android/gradlew')); |
|
} |
|
|
|
/** |
|
* Starts the app on a connected Android emulator or device. |
|
*/ |
|
function runAndroid(argv, config, args) { |
|
if (!checkAndroid(args.root)) { |
|
const reactNativeScriptsPath = findReactNativeScripts(); |
|
if (reactNativeScriptsPath) { |
|
child_process.spawnSync( |
|
reactNativeScriptsPath, |
|
['android'].concat(process.argv.slice(1)), |
|
{stdio: 'inherit'} |
|
); |
|
} else { |
|
console.log(chalk.red('Android project not found. Maybe run react-native android first?')); |
|
} |
|
return; |
|
} |
|
|
|
if (!args.packager) { |
|
return buildAndRun(args); |
|
} |
|
|
|
return isPackagerRunning(args.port).then(result => { |
|
if (result === 'running') { |
|
console.log(chalk.bold('JS server already running.')); |
|
} else if (result === 'unrecognized') { |
|
console.warn(chalk.yellow('JS server not recognized, continuing with build...')); |
|
} else { |
|
// result == 'not_running' |
|
console.log(chalk.bold('Starting JS server...')); |
|
startServerInNewWindow(args.port); |
|
} |
|
return buildAndRun(args); |
|
}); |
|
} |
|
|
|
function getAdbPath() { |
|
return process.env.ANDROID_HOME |
|
? process.env.ANDROID_HOME + '/platform-tools/adb' |
|
: 'adb'; |
|
} |
|
|
|
// Runs ADB reverse tcp:8081 tcp:8081 to allow loading the jsbundle from the packager |
|
function tryRunAdbReverse(packagerPort, device) { |
|
try { |
|
const adbPath = getAdbPath(); |
|
const adbArgs = ['reverse', `tcp:${packagerPort}`, `tcp:${packagerPort}`]; |
|
|
|
// If a device is specified then tell adb to use it |
|
if (device) { |
|
adbArgs.unshift('-s', device); |
|
} |
|
|
|
console.log(chalk.bold( |
|
`Running ${adbPath} ${adbArgs.join(' ')}` |
|
)); |
|
|
|
child_process.execFileSync(adbPath, adbArgs, { |
|
stdio: [process.stdin, process.stdout, process.stderr], |
|
}); |
|
} catch (e) { |
|
console.log(chalk.yellow(`Could not run adb reverse: ${e.message}`)); |
|
} |
|
} |
|
|
|
function getPackageNameWithSuffix(appId, appIdSuffix, packageName) { |
|
if (appId) { |
|
return appId; |
|
} else if (appIdSuffix) { |
|
return packageName + '.' + appIdSuffix; |
|
} |
|
|
|
return packageName; |
|
} |
|
|
|
// Builds the app and runs it on a connected emulator / device. |
|
function buildAndRun(args) { |
|
process.chdir(path.join(args.root, 'android')); |
|
const cmd = process.platform.startsWith('win') |
|
? 'gradlew.bat' |
|
: './gradlew'; |
|
|
|
const packageName = fs.readFileSync( |
|
`${args.appFolder}/src/main/AndroidManifest.xml`, |
|
'utf8' |
|
).match(/package="(.+?)"/)[1]; |
|
|
|
const packageNameWithSuffix = getPackageNameWithSuffix(args.appId, args.appIdSuffix, packageName); |
|
|
|
const adbPath = getAdbPath(); |
|
if (args.deviceId) { |
|
if (isString(args.deviceId)) { |
|
runOnSpecificDevice(args, cmd, packageNameWithSuffix, packageName, adbPath); |
|
} else { |
|
console.log(chalk.red('Argument missing for parameter --deviceId')); |
|
} |
|
} else { |
|
runOnAllDevices(args, cmd, packageNameWithSuffix, packageName, adbPath); |
|
} |
|
} |
|
|
|
function runOnSpecificDevice(args, gradlew, packageNameWithSuffix, packageName, adbPath) { |
|
let devices = adb.getDevices(); |
|
if (devices && devices.length > 0) { |
|
if (devices.indexOf(args.deviceId) !== -1) { |
|
buildApk(gradlew); |
|
installAndLaunchOnDevice(args, args.deviceId, packageNameWithSuffix, packageName, adbPath); |
|
} else { |
|
console.log('Could not find device with the id: "' + args.deviceId + '".'); |
|
console.log('Choose one of the following:'); |
|
console.log(devices); |
|
} |
|
} else { |
|
console.log('No Android devices connected.'); |
|
} |
|
} |
|
|
|
function buildApk(gradlew) { |
|
try { |
|
console.log(chalk.bold('Building the app...')); |
|
|
|
// using '-x lint' in order to ignore linting errors while building the apk |
|
child_process.execFileSync(gradlew, ['build', '-x', 'lint'], { |
|
stdio: [process.stdin, process.stdout, process.stderr], |
|
}); |
|
} catch (e) { |
|
console.log(chalk.red('Could not build the app, read the error above for details.\n')); |
|
} |
|
} |
|
|
|
function tryInstallAppOnDevice(args, device) { |
|
try { |
|
const pathToApk = `${args.appFolder}/build/outputs/apk/${args.appFolder}-debug.apk`; |
|
const adbPath = getAdbPath(); |
|
const adbArgs = ['-s', device, 'install', pathToApk]; |
|
console.log(chalk.bold( |
|
`Installing the app on the device (cd android && adb -s ${device} install ${pathToApk}` |
|
)); |
|
child_process.execFileSync(adbPath, adbArgs, { |
|
stdio: [process.stdin, process.stdout, process.stderr], |
|
}); |
|
} catch (e) { |
|
console.log(e.message); |
|
console.log(chalk.red( |
|
'Could not install the app on the device, read the error above for details.\n' |
|
)); |
|
} |
|
} |
|
|
|
function tryLaunchAppOnDevice(device, packageNameWithSuffix, packageName, adbPath, mainActivity) { |
|
try { |
|
const adbArgs = ['-s', device, 'shell', 'am', 'start', '-n', packageNameWithSuffix + '/' + packageName + '.' + mainActivity]; |
|
console.log(chalk.bold( |
|
`Starting the app on ${device} (${adbPath} ${adbArgs.join(' ')})...` |
|
)); |
|
child_process.spawnSync(adbPath, adbArgs, {stdio: 'inherit'}); |
|
} catch (e) { |
|
console.log(chalk.red( |
|
'adb invocation failed. Do you have adb in your PATH?' |
|
)); |
|
} |
|
} |
|
|
|
function installAndLaunchOnDevice(args, selectedDevice, packageNameWithSuffix, packageName, adbPath) { |
|
tryRunAdbReverse(args.port, selectedDevice); |
|
tryInstallAppOnDevice(args, selectedDevice); |
|
tryLaunchAppOnDevice(selectedDevice, packageNameWithSuffix, packageName, adbPath, args.mainActivity); |
|
} |
|
|
|
function runOnAllDevices(args, cmd, packageNameWithSuffix, packageName, adbPath){ |
|
try { |
|
const gradleArgs = []; |
|
if (args.variant) { |
|
gradleArgs.push('install' + |
|
args.variant[0].toUpperCase() + args.variant.slice(1) |
|
); |
|
} else if (args.flavor) { |
|
console.warn(chalk.yellow( |
|
'--flavor has been deprecated. Use --variant instead' |
|
)); |
|
gradleArgs.push('install' + |
|
args.flavor[0].toUpperCase() + args.flavor.slice(1) |
|
); |
|
} else { |
|
gradleArgs.push('installDebug'); |
|
} |
|
|
|
if (args.installDebug) { |
|
gradleArgs.push(args.installDebug); |
|
} |
|
|
|
console.log(chalk.bold( |
|
`Building and installing the app on the device (cd android && ${cmd} ${gradleArgs.join(' ')})...` |
|
)); |
|
|
|
child_process.execFileSync(cmd, gradleArgs, { |
|
stdio: [process.stdin, process.stdout, process.stderr], |
|
}); |
|
} catch (e) { |
|
console.log(chalk.red( |
|
'Could not install the app on the device, read the error above for details.\n' + |
|
'Make sure you have an Android emulator running or a device connected and have\n' + |
|
'set up your Android development environment:\n' + |
|
'https://facebook.github.io/react-native/docs/getting-started.html' |
|
)); |
|
// stderr is automatically piped from the gradle process, so the user |
|
// should see the error already, there is no need to do |
|
// `console.log(e.stderr)` |
|
return Promise.reject(); |
|
} |
|
const devices = adb.getDevices(); |
|
if (devices && devices.length > 0) { |
|
devices.forEach((device) => { |
|
tryRunAdbReverse(args.port, device); |
|
tryLaunchAppOnDevice(device, packageNameWithSuffix, packageName, adbPath, args.mainActivity); |
|
}); |
|
} else { |
|
try { |
|
// If we cannot execute based on adb devices output, fall back to |
|
// shell am start |
|
const fallbackAdbArgs = [ |
|
'shell', 'am', 'start', '-n', packageNameWithSuffix + '/' + packageName + '.MainActivity' |
|
]; |
|
console.log(chalk.bold( |
|
`Starting the app (${adbPath} ${fallbackAdbArgs.join(' ')}...` |
|
)); |
|
child_process.spawnSync(adbPath, fallbackAdbArgs, {stdio: 'inherit'}); |
|
} catch (e) { |
|
console.log(chalk.red( |
|
'adb invocation failed. Do you have adb in your PATH?' |
|
)); |
|
// stderr is automatically piped from the gradle process, so the user |
|
// should see the error already, there is no need to do |
|
// `console.log(e.stderr)` |
|
return Promise.reject(); |
|
} |
|
} |
|
} |
|
|
|
function startServerInNewWindow(port) { |
|
const scriptFile = /^win/.test(process.platform) ? |
|
'launchPackager.bat' : |
|
'launchPackager.command'; |
|
const scriptsDir = path.resolve(__dirname, '..', '..', 'scripts'); |
|
const launchPackagerScript = path.resolve(scriptsDir, scriptFile); |
|
const procConfig = {cwd: scriptsDir}; |
|
const terminal = process.env.REACT_TERMINAL; |
|
|
|
// setup the .packager.env file to ensure the packager starts on the right port |
|
const packagerEnvFile = path.join(__dirname, '..', '..', 'scripts', '.packager.env'); |
|
const content = `export RCT_METRO_PORT=${port}`; |
|
// ensure we overwrite file by passing the 'w' flag |
|
fs.writeFileSync(packagerEnvFile, content, {encoding: 'utf8', flag: 'w'}); |
|
|
|
if (process.platform === 'darwin') { |
|
if (terminal) { |
|
return child_process.spawnSync('open', ['-a', terminal, launchPackagerScript], procConfig); |
|
} |
|
return child_process.spawnSync('open', [launchPackagerScript], procConfig); |
|
|
|
} else if (process.platform === 'linux') { |
|
procConfig.detached = true; |
|
if (terminal){ |
|
return child_process.spawn(terminal, ['-e', 'sh ' + launchPackagerScript], procConfig); |
|
} |
|
return child_process.spawn('sh', [launchPackagerScript], procConfig); |
|
|
|
} else if (/^win/.test(process.platform)) { |
|
procConfig.detached = true; |
|
procConfig.stdio = 'ignore'; |
|
return child_process.spawn('cmd.exe', ['/C', launchPackagerScript], procConfig); |
|
} else { |
|
console.log(chalk.red(`Cannot start the packager. Unknown platform ${process.platform}`)); |
|
} |
|
} |
|
|
|
module.exports = { |
|
name: 'run-android', |
|
description: 'builds your app and starts it on a connected Android emulator or device', |
|
func: runAndroid, |
|
options: [{ |
|
command: '--install-debug', |
|
}, { |
|
command: '--root [string]', |
|
description: 'Override the root directory for the android build (which contains the android directory)', |
|
default: '', |
|
}, { |
|
command: '--flavor [string]', |
|
description: '--flavor has been deprecated. Use --variant instead', |
|
}, { |
|
command: '--variant [string]', |
|
}, { |
|
command: '--appFolder [string]', |
|
description: 'Specify a different application folder name for the android source.', |
|
default: 'app', |
|
}, { |
|
command: '--appId [string]', |
|
description: 'Specify an applicationId to launch after build.', |
|
default: '', |
|
}, { |
|
command: '--appIdSuffix [string]', |
|
description: 'Specify an applicationIdSuffix to launch after build.', |
|
default: '', |
|
}, { |
|
command: '--main-activity [string]', |
|
description: 'Name of the activity to start', |
|
default: 'MainActivity', |
|
}, { |
|
command: '--deviceId [string]', |
|
description: 'builds your app and starts it on a specific device/simulator with the ' + |
|
'given device id (listed by running "adb devices" on the command line).', |
|
}, { |
|
command: '--no-packager', |
|
description: 'Do not launch packager while building', |
|
}, { |
|
command: '--port [number]', |
|
default: process.env.RCT_METRO_PORT || 8081, |
|
parse: (val: string) => Number(val), |
|
}], |
|
};
|
|
|