diff --git a/androidProjectHelpers.js b/androidProjectHelpers.js index e6a772a0..58ce9fa4 100644 --- a/androidProjectHelpers.js +++ b/androidProjectHelpers.js @@ -47,6 +47,16 @@ const getMksnapshotParams = (projectDir) => { } }; +const getRuntimeNdkRevision = (projectDir) => { + try { + const androidSettingsJSON = getAndroidSettingsJson(projectDir); + const result = androidSettingsJSON && androidSettingsJSON.ndkRevision; + return result; + } catch (e) { + return null; + } +}; + const getAndroidSettingsJson = projectDir => { const androidSettingsJsonPath = resolve(projectDir, PLATFORMS_ANDROID, "settings.json"); if (existsSync(androidSettingsJsonPath)) { @@ -62,5 +72,6 @@ module.exports = { ANDROID_CONFIGURATIONS_PATH, getAndroidRuntimeVersion, getAndroidV8Version, - getMksnapshotParams -}; \ No newline at end of file + getMksnapshotParams, + getRuntimeNdkRevision +}; diff --git a/lib/utils.js b/lib/utils.js index 2e7bd58d..8876252a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -15,8 +15,15 @@ function isWinOS() { return os.type() === "Windows_NT"; } +function warn(message) { + if (message) { + console.log(`\x1B[33;1m${message}\x1B[0m`); + } +} + module.exports = { shouldSnapshot, convertToUnixPath, - isWinOS + isWinOS, + warn }; diff --git a/package.json b/package.json index 23cff215..1883e191 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "minimatch": "3.0.4", "nativescript-hook": "0.2.4", "nativescript-worker-loader": "~0.9.0", + "properties-reader": "0.3.1", "proxy-lib": "0.4.0", "raw-loader": "~0.5.1", "request": "2.88.0", diff --git a/snapshot/android/project-snapshot-generator.js b/snapshot/android/project-snapshot-generator.js index 1251b751..32749337 100644 --- a/snapshot/android/project-snapshot-generator.js +++ b/snapshot/android/project-snapshot-generator.js @@ -1,14 +1,12 @@ -const { dirname, isAbsolute, join, resolve, sep } = require("path"); -const { existsSync, readFileSync, writeFileSync } = require("fs"); +const { isAbsolute, join, resolve, sep } = require("path"); +const { readFileSync, writeFileSync } = require("fs"); const shelljs = require("shelljs"); const semver = require("semver"); const SnapshotGenerator = require("./snapshot-generator"); const { - CONSTANTS, - createDirectory, - getJsonFile, + CONSTANTS } = require("./utils"); const { ANDROID_PROJECT_DIR, @@ -16,14 +14,13 @@ const { ANDROID_CONFIGURATIONS_PATH, getAndroidRuntimeVersion, getAndroidV8Version, + getRuntimeNdkRevision, getMksnapshotParams } = require("../../androidProjectHelpers"); -const MIN_ANDROID_RUNTIME_VERSION = "3.0.0"; +// min version with settings.json file specifying the V8 version +const MIN_ANDROID_RUNTIME_VERSION = "5.2.1"; const VALID_ANDROID_RUNTIME_TAGS = Object.freeze(["next", "rc"]); -const V8_VERSIONS_FILE_NAME = "v8-versions.json"; -const V8_VERSIONS_URL = `https://raw.githubusercontent.com/NativeScript/android-runtime/master/${V8_VERSIONS_FILE_NAME}`; -const V8_VERSIONS_LOCAL_PATH = resolve(CONSTANTS.SNAPSHOT_TMP_DIR, V8_VERSIONS_FILE_NAME); const resolveRelativePath = (path) => { if (path) @@ -120,73 +117,6 @@ ProjectSnapshotGenerator.installSnapshotArtefacts = function (projectRoot) { } } -const versionIsPrerelease = version => version.indexOf("-") > -1; -const v8VersionsFileExists = () => existsSync(V8_VERSIONS_LOCAL_PATH); -const saveV8VersionsFile = versionsMap => - writeFileSync(V8_VERSIONS_LOCAL_PATH, JSON.stringify(versionsMap)); -const readV8VersionsFile = () => JSON.parse(readFileSync(V8_VERSIONS_LOCAL_PATH)); -const fetchV8VersionsFile = () => - new Promise((resolve, reject) => { - getJsonFile(V8_VERSIONS_URL) - .then(versionsMap => { - createDirectory(dirname(V8_VERSIONS_LOCAL_PATH)); - saveV8VersionsFile(versionsMap); - return resolve(versionsMap); - }) - .catch(reject); - }); - -const findV8Version = (runtimeVersion, v8VersionsMap) => { - const runtimeRange = Object.keys(v8VersionsMap) - .find(range => semver.satisfies(runtimeVersion, range)); - - return v8VersionsMap[runtimeRange]; -} - -const getV8VersionsMap = runtimeVersion => - new Promise((resolve, reject) => { - if (!v8VersionsFileExists() || versionIsPrerelease(runtimeVersion)) { - fetchV8VersionsFile() - .then(versionsMap => resolve({ versionsMap, latest: true })) - .catch(reject); - } else { - const versionsMap = readV8VersionsFile(); - return resolve({ versionsMap, latest: false }); - } - }); - -ProjectSnapshotGenerator.prototype.getV8Version = function (generationOptions) { - return new Promise((resolve, reject) => { - const maybeV8Version = generationOptions.v8Version; - if (maybeV8Version) { - return resolve(maybeV8Version); - } - - // try to get the V8 Version from the settings.json file in android runtime folder - const runtimeV8Version = getAndroidV8Version(this.options.projectRoot); - if (runtimeV8Version) { - return resolve(runtimeV8Version); - } - - const runtimeVersion = getAndroidRuntimeVersion(this.options.projectRoot); - getV8VersionsMap(runtimeVersion) - .then(({ versionsMap, latest }) => { - const v8Version = findV8Version(runtimeVersion, versionsMap); - - if (!v8Version && !latest) { - fetchV8VersionsFile().then(latestVersionsMap => { - const version = findV8Version(runtimeVersion, latestVersionsMap) - return resolve(version); - }) - .catch(reject); - } else { - return resolve(v8Version); - } - }) - .catch(reject); - }); -} - ProjectSnapshotGenerator.prototype.validateAndroidRuntimeVersion = function () { const currentRuntimeVersion = getAndroidRuntimeVersion(this.options.projectRoot); @@ -224,53 +154,45 @@ ProjectSnapshotGenerator.prototype.generate = function (generationOptions) { // Generate snapshots const generator = new SnapshotGenerator({ buildPath: this.getBuildPath() }); - const noV8VersionFoundMessage = `Cannot find suitable v8 version!`; - let shouldRethrow = false; - const mksnapshotParams = getMksnapshotParams(this.options.projectRoot); + const recommendedAndroidNdkRevision = getRuntimeNdkRevision(this.options.projectRoot); + const v8Version = generationOptions.v8Version || getAndroidV8Version(this.options.projectRoot); + if (!v8Version) { + throw new Error(noV8VersionFoundMessage); + } - return this.getV8Version(generationOptions).then(v8Version => { - shouldRethrow = true; - if (!v8Version) { - throw new Error(noV8VersionFoundMessage); - } + // NOTE: Order is important! Add new archs at the end of the array + const defaultTargetArchs = ["arm", "arm64", "ia32", "ia64"]; + const runtimeVersion = getAndroidRuntimeVersion(this.options.projectRoot); + if (runtimeVersion && semver.lt(semver.coerce(runtimeVersion), "6.0.2")) { + const indexOfIa64 = defaultTargetArchs.indexOf("ia64"); + // Before 6.0.2 version of Android runtime we supported only arm, arm64 and ia32. + defaultTargetArchs.splice(indexOfIa64, defaultTargetArchs.length - indexOfIa64); + } - // NOTE: Order is important! Add new archs at the end of the array - const defaultTargetArchs = ["arm", "arm64", "ia32", "ia64"]; - const runtimeVersion = getAndroidRuntimeVersion(this.options.projectRoot); - if (runtimeVersion && semver.lt(semver.coerce(runtimeVersion), "6.0.2")) { - const indexOfIa64 = defaultTargetArchs.indexOf("ia64"); - // Before 6.0.2 version of Android runtime we supported only arm, arm64 and ia32. - defaultTargetArchs.splice(indexOfIa64, defaultTargetArchs.length - indexOfIa64); + const options = { + snapshotToolsPath, + targetArchs: generationOptions.targetArchs || defaultTargetArchs, + v8Version: generationOptions.v8Version || v8Version, + preprocessedInputFile: generationOptions.preprocessedInputFile, + useLibs: generationOptions.useLibs || false, + inputFiles: generationOptions.inputFiles || [join(this.options.projectRoot, "__snapshot.js")], + androidNdkPath, + mksnapshotParams: mksnapshotParams, + snapshotInDocker: generationOptions.snapshotInDocker, + recommendedAndroidNdkRevision + }; + + return generator.generate(options).then(() => { + console.log("Snapshots build finished succesfully!"); + + if (generationOptions.install) { + ProjectSnapshotGenerator.cleanSnapshotArtefacts(this.options.projectRoot); + ProjectSnapshotGenerator.installSnapshotArtefacts(this.options.projectRoot); + console.log(generationOptions.useLibs ? + "Snapshot is included in the app as dynamically linked library (.so file)." : + "Snapshot is included in the app as binary .blob file. The more space-efficient option is to embed it in a dynamically linked library (.so file)."); } - - const options = { - snapshotToolsPath, - targetArchs: generationOptions.targetArchs || defaultTargetArchs, - v8Version: generationOptions.v8Version || v8Version, - preprocessedInputFile: generationOptions.preprocessedInputFile, - useLibs: generationOptions.useLibs || false, - inputFiles: generationOptions.inputFiles || [join(this.options.projectRoot, "__snapshot.js")], - androidNdkPath, - mksnapshotParams: mksnapshotParams, - snapshotInDocker: generationOptions.snapshotInDocker - }; - - return generator.generate(options).then(() => { - console.log("Snapshots build finished succesfully!"); - - if (generationOptions.install) { - ProjectSnapshotGenerator.cleanSnapshotArtefacts(this.options.projectRoot); - ProjectSnapshotGenerator.installSnapshotArtefacts(this.options.projectRoot); - console.log(generationOptions.useLibs ? - "Snapshot is included in the app as dynamically linked library (.so file)." : - "Snapshot is included in the app as binary .blob file. The more space-efficient option is to embed it in a dynamically linked library (.so file)."); - } - }); - }).catch(error => { - throw shouldRethrow ? - error : - new Error(`${noV8VersionFoundMessage} Original error: ${error.message || error}`); - }); + });; } diff --git a/snapshot/android/snapshot-generator.js b/snapshot/android/snapshot-generator.js index 06ca9f41..9a15e6ee 100644 --- a/snapshot/android/snapshot-generator.js +++ b/snapshot/android/snapshot-generator.js @@ -1,8 +1,8 @@ const fs = require("fs"); const { dirname, relative, join, EOL } = require("path"); const child_process = require("child_process"); -const { convertToUnixPath } = require("../../lib/utils"); - +const { convertToUnixPath, warn } = require("../../lib/utils"); +const PropertiesReader = require('properties-reader'); const shelljs = require("shelljs"); const { createDirectory, downloadFile, getHostOS, getHostOSArch, CONSTANTS, has32BitArch, isMacOSCatalinaOrHigher, isSubPath } = require("./utils"); @@ -192,8 +192,9 @@ SnapshotGenerator.prototype.setupDocker = function () { child_process.execSync(`docker pull ${SNAPSHOTS_DOCKER_IMAGE}`); } -SnapshotGenerator.prototype.buildSnapshotLibs = function (androidNdkBuildPath, targetArchs) { +SnapshotGenerator.prototype.buildSnapshotLibs = function (androidNdkPath, recommendedAndroidNdkRevision, targetArchs) { // Compile *.c files to produce *.so libraries with ndk-build tool + const androidNdkBuildPath = this.getAndroidNdkBuildPath(androidNdkPath, recommendedAndroidNdkRevision); const ndkBuildPath = join(this.buildPath, "ndk-build"); const androidArchs = targetArchs.map(arch => this.convertToAndroidArchName(arch)); console.log("Building native libraries for " + androidArchs.join()); @@ -207,6 +208,54 @@ SnapshotGenerator.prototype.buildSnapshotLibs = function (androidNdkBuildPath, t return join(ndkBuildPath, "libs"); } +SnapshotGenerator.prototype.getAndroidNdkBuildPath = function (androidNdkPath, recommendedAndroidNdkRevision) { + const ndkBuildExecutableName = "ndk-build"; + // fallback for Android Runtime < 6.2.0 with the 6.1.0 value + recommendedAndroidNdkRevision = recommendedAndroidNdkRevision || "20.0.5594570"; + let androidNdkBuildPath = ""; + if (androidNdkPath) { + // specified by the user + const localNdkRevision = this.getAndroidNdkRevision(androidNdkPath); + androidNdkBuildPath = join(androidNdkPath, ndkBuildExecutableName); + if (!fs.existsSync(androidNdkBuildPath)) { + throw new Error(`The provided Android NDK path does not contain ${ndkBuildExecutableName} executable.`); + } else if (localNdkRevision !== recommendedAndroidNdkRevision) { + warn(`The provided Android NDK is v${localNdkRevision} while the recommended one is v${recommendedAndroidNdkRevision}`); + } + } else { + // available globally + let hasAndroidNdkInPath = true; + androidNdkBuildPath = ndkBuildExecutableName; + try { + child_process.execSync(`${androidNdkBuildPath} --version`); + console.log(`Cannot determine the version of the global Android NDK. The recommended versions is v${recommendedAndroidNdkRevision}`); + } catch (_) { + hasAndroidNdkInPath = false; + } + + if (!hasAndroidNdkInPath) { + // installed in ANDROID_HOME + const androidHome = process.env.ANDROID_HOME; + androidNdkBuildPath = join(androidHome, "ndk", recommendedAndroidNdkRevision, ndkBuildExecutableName); + if (!fs.existsSync(androidNdkBuildPath)) { + throw new Error(`Android NDK v${recommendedAndroidNdkRevision} is not installed. You can find installation instructions in this article: https://developer.android.com/studio/projects/install-ndk#specific-version`); + } + } + } + + return androidNdkBuildPath; +} + +SnapshotGenerator.prototype.getAndroidNdkRevision = function (androidNdkPath) { + const ndkPropertiesFile = join(androidNdkPath, "source.properties"); + if (fs.existsSync(ndkPropertiesFile)) { + const properties = PropertiesReader(ndkPropertiesFile); + return properties.get("Pkg.Revision"); + } else { + return null; + } +} + SnapshotGenerator.prototype.buildIncludeGradle = function () { shelljs.cp(INCLUDE_GRADLE_PATH, join(this.buildPath, "include.gradle")); } @@ -236,8 +285,7 @@ SnapshotGenerator.prototype.generate = function (options) { ).then(() => { this.buildIncludeGradle(); if (options.useLibs) { - const androidNdkBuildPath = options.androidNdkPath ? join(options.androidNdkPath, "ndk-build") : "ndk-build"; - this.buildSnapshotLibs(androidNdkBuildPath, options.targetArchs); + this.buildSnapshotLibs(options.androidNdkPath, options.recommendedAndroidNdkRevision, options.targetArchs); } return this.buildPath; }); diff --git a/templates/webpack.angular.js b/templates/webpack.angular.js index 7011f3c2..593b27d0 100644 --- a/templates/webpack.angular.js +++ b/templates/webpack.angular.js @@ -52,9 +52,11 @@ module.exports = env => { unitTesting, // --env.unitTesting verbose, // --env.verbose snapshotInDocker, // --env.snapshotInDocker - skipSnapshotTools // --env.skipSnapshotTools + skipSnapshotTools, // --env.skipSnapshotTools + compileSnapshot // --env.compileSnapshot } = env; + const useLibs = compileSnapshot; const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap; const externals = nsWebpack.getConvertedExternals(env.externals); const appFullPath = resolve(projectRoot, appPath); @@ -315,7 +317,8 @@ module.exports = env => { projectRoot, webpackConfig: config, snapshotInDocker, - skipSnapshotTools + skipSnapshotTools, + useLibs })); } diff --git a/templates/webpack.javascript.js b/templates/webpack.javascript.js index 4a57ca28..3f669da1 100644 --- a/templates/webpack.javascript.js +++ b/templates/webpack.javascript.js @@ -45,9 +45,11 @@ module.exports = env => { unitTesting, // --env.unitTesting, verbose, // --env.verbose snapshotInDocker, // --env.snapshotInDocker - skipSnapshotTools // --env.skipSnapshotTools + skipSnapshotTools, // --env.skipSnapshotTools + compileSnapshot // --env.compileSnapshot } = env; + const useLibs = compileSnapshot; const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap; const externals = nsWebpack.getConvertedExternals(env.externals); const appFullPath = resolve(projectRoot, appPath); @@ -253,7 +255,8 @@ module.exports = env => { projectRoot, webpackConfig: config, snapshotInDocker, - skipSnapshotTools + skipSnapshotTools, + useLibs })); } diff --git a/templates/webpack.typescript.js b/templates/webpack.typescript.js index 72639536..50249817 100644 --- a/templates/webpack.typescript.js +++ b/templates/webpack.typescript.js @@ -47,8 +47,11 @@ module.exports = env => { unitTesting, // --env.unitTesting, verbose, // --env.verbose snapshotInDocker, // --env.snapshotInDocker - skipSnapshotTools // --env.skipSnapshotTools + skipSnapshotTools, // --env.skipSnapshotTools + compileSnapshot // --env.compileSnapshot } = env; + + const useLibs = compileSnapshot; const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap; const externals = nsWebpack.getConvertedExternals(env.externals); @@ -285,7 +288,8 @@ module.exports = env => { projectRoot, webpackConfig: config, snapshotInDocker, - skipSnapshotTools + skipSnapshotTools, + useLibs })); } diff --git a/templates/webpack.vue.js b/templates/webpack.vue.js index 7de53cb9..edb031d4 100644 --- a/templates/webpack.vue.js +++ b/templates/webpack.vue.js @@ -48,9 +48,11 @@ module.exports = env => { unitTesting, // --env.unitTesting verbose, // --env.verbose snapshotInDocker, // --env.snapshotInDocker - skipSnapshotTools // --env.skipSnapshotTools + skipSnapshotTools, // --env.skipSnapshotTools + compileSnapshot // --env.compileSnapshot } = env; + const useLibs = compileSnapshot; const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap; const externals = nsWebpack.getConvertedExternals(env.externals); @@ -300,7 +302,8 @@ module.exports = env => { projectRoot, webpackConfig: config, snapshotInDocker, - skipSnapshotTools + skipSnapshotTools, + useLibs })); }