From baabf1e7a96c30a30c098f6e3682266e0ca87663 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Sun, 28 Jun 2020 15:34:27 +0100 Subject: [PATCH] load single android library cache from local folder --- extension.js | 4 +- langserver/java/java-libraries.js | 179 ++++++------------------------ langserver/server.js | 22 ++-- 3 files changed, 48 insertions(+), 157 deletions(-) diff --git a/extension.js b/extension.js index 4b7d0ab..5f59937 100644 --- a/extension.js +++ b/extension.js @@ -42,8 +42,8 @@ function activateLanguageClient(context) { // Register the server for plain text documents documentSelector: [{ scheme: 'file', language: 'java' }], initializationOptions: { - // globalStoragePath is used to cache decoded jar files - globalStoragePath: context.globalStoragePath, + // extensionPath points to the root of the extension (the folder where this file is) + extensionPath: context.extensionPath, }, synchronize: { // Notify the server about file changes to '.java files contained in the workspace diff --git a/langserver/java/java-libraries.js b/langserver/java/java-libraries.js index 11f3272..dad1155 100644 --- a/langserver/java/java-libraries.js +++ b/langserver/java/java-libraries.js @@ -1,174 +1,61 @@ const fs = require('fs'); const path = require('path'); -const { loadAndroidLibrary, CEIType } = require('java-mti'); - -const android_system_library_cache_filename = { - regex: /^sdk-platforms-android-(.+?)-(\d+)\.json$/, - /** - * @param {string} version - * @param {fs.Stats} stat - */ - build(version, stat) { - return `sdk-platforms-${version}-${Math.trunc(stat.mtime.getTime())}.json`; - } -} +const { CEIType, loadAndroidLibrary } = require('java-mti'); /** - * @param {string} cache_dir directory used to store decoded jar libraries + * @param {string} extensionPath install path of extension + * @returns {Promise>} */ -function ensureFolderExists(cache_dir) { - if (!cache_dir) { - throw new Error('missing cache dir value'); - } +async function loadAndroidSystemLibrary(extensionPath) { + console.time('android-library-load'); + let library; try { - const stat = fs.statSync(cache_dir); - if (!stat.isDirectory()) { - throw new Error(`cache dir '${cache_dir}' is not a directory`); + if (!extensionPath) { + throw new Error('Missing extension path') } - } catch (err) { - if (err.code !== 'ENOENT') { - throw new Error(`cache dir '${cache_dir}' check failed: ${err.message}`); - } - fs.mkdirSync(cache_dir); + const cache_folder = path.join(extensionPath, 'langserver', '.library-cache'); + library = await loadHighestAPIPlatform(cache_folder); + } finally { + console.timeEnd('android-library-load'); } + return library; } /** - * - * @param {string} cache_dir - * @param {RegExp} filter - * @returns {fs.Dirent[]} + * @param {string} cache_folder */ -function findLibraryFiles(cache_dir, filter) { - const files = fs.readdirSync(cache_dir, { withFileTypes: true }); - const valid_files = files.filter(file => filter.test(file.name) && file.isFile()); - return valid_files; -} +async function loadHighestAPIPlatform(cache_folder) { + /** @type {fs.Dirent[]} */ + const files = await new Promise((res, rej) => { + fs.readdir(cache_folder, {withFileTypes: true}, (err, files) => err ? rej(err) : res(files)); + }); -/** - * - * @param {fs.Dirent[]} files - */ -function chooseAndroidSystemLibrary(files) { - let chosen = { + // find the file with the highest API level + let best_match = { api: 0, /** @type {fs.Dirent} */ file: null, }; files.forEach(file => { - const m = file.name.match(android_system_library_cache_filename.regex); + const m = file.name.match(/^android-(\d+)\.zip$/); if (!m) return; - if (/^\d+$/.test(m[1])) { - const api = parseInt(m[1]); - if (api > chosen.api) { - chosen.api = api; - chosen.file = file; + const api = parseInt(m[1], 10); + if (api > best_match.api) { + best_match = { + api, + file, } } - }) - return chosen.file; -} - -function findHighestAPISystemLibrary(android_sdk_platforms_root) { - let platform_folders = [], android_platform_jars = []; - try { - platform_folders = fs.readdirSync(android_sdk_platforms_root, { withFileTypes: true }); - } catch {}; - platform_folders.forEach(folder => { - if (!folder.isDirectory()) return; - // we assume stable SDK platform folders are named 'android-' - if (!/^android-\d+$/.test(folder.name)) return; - // the platform folder must contain an android.jar file - let stat, filepath = path.join(android_sdk_platforms_root, folder.name, 'android.jar'); - try { stat = fs.statSync(filepath) } - catch { return } - if (!stat.isFile()) return; - // add it to the list - android_platform_jars.push({ - folder: folder.name, - api: parseInt(folder.name.split('-')[1], 10), - filepath, - stat, - }) }); - if (android_platform_jars.length === 0) { - return null; + if (!best_match.file) { + throw new Error(`No valid platform cache files found in ${cache_folder}`) } - // choose the folder with the highest API number - return android_platform_jars.sort((a,b) => b.api - a.api)[0].folder; -} + console.log(`loading android platform cache: ${best_match.file.name}`); -/** - * @param {string} cache_dir - * @param {string|':latest'} [version] - */ -async function buildAndroidSystemLibrary(cache_dir, version) { - const android_sdk_root = process.env['ANDROID_SDK'] || process.env['ANDROID_HOME']; - if (!android_sdk_root) { - throw new Error('Cannot locate Android SDK folder - ANDROID_SDK env variable is not defined'); - } - const android_sdk_platforms_root = path.join(android_sdk_root, 'platforms'); + const cache_file = path.join(cache_folder, best_match.file.name); + const typemap = loadAndroidLibrary(cache_file); - if (!version) { - // choose the folder with the highest API number - version = findHighestAPISystemLibrary(android_sdk_platforms_root); - if (!version) { - throw new Error(`Cannot build Android library: No supported system libraries found in ${android_sdk_platforms_root}`); - } - } - - let stat, filepath = path.join(android_sdk_platforms_root, version, 'android.jar'); - try { stat = fs.statSync(filepath) } - catch { } - if (!stat || !stat.isFile()) { - throw new Error(`Cannot build Android library: '${filepath}' is not a valid android system library file`); - } - - console.log(`Building ${version} library cache for code completion support. This might take a few minutes...`); - const cache_filename = path.join(cache_dir, android_system_library_cache_filename.build(version, stat)); - try { - const library = await loadAndroidLibrary(cache_filename, { api: version, sdk_root: android_sdk_root }); - console.log(`${version} library cache built.`); - return library; - } catch(err) { - throw new Error(`Cannot build Android library: ${err.message}`); - } -} - -/** - * @param {string} cache_dir directory used to store decoded jar libraries - * @returns {Promise>} - */ -async function loadAndroidSystemLibrary(cache_dir) { - console.time('android-library-load'); - // for (let x;;) { - // console.log('waiting'); - // if (x) break; - // await new Promise(res => setTimeout(res, 1000)); - // } - let library; - try { - ensureFolderExists(cache_dir); - const library_files = findLibraryFiles(cache_dir, android_system_library_cache_filename.regex); - if (!library_files.length) { - return buildAndroidSystemLibrary(cache_dir); - } - // search for the highest android API number in the list of cached files - const library_file = chooseAndroidSystemLibrary(library_files); - if (!library_file) { - return buildAndroidSystemLibrary(cache_dir); - } - // load the library - const library_path_name = path.join(cache_dir, library_file.name); - console.log(`Loading android system library: ${library_path_name}`); - library = await loadAndroidLibrary(library_path_name, null); - - } catch (err) { - console.error(`android library load failed: ${err.message}`); - library = new Map(); - } - console.timeEnd('android-library-load'); - return library; + return typemap; } exports.loadAndroidSystemLibrary = loadAndroidSystemLibrary; diff --git a/langserver/server.js b/langserver/server.js index 7a87a65..ebaf334 100644 --- a/langserver/server.js +++ b/langserver/server.js @@ -173,9 +173,6 @@ let first_parse_waiting = new Set(); */ function reparse(uris, opts) { trace('reparse'); - if (androidLibrary instanceof Promise) { - return; - } if (!uris || !uris.length) { return; } @@ -186,6 +183,11 @@ function reparse(uris, opts) { return; } } + if (androidLibrary instanceof Promise) { + // reparse after the library has loaded + androidLibrary.then(() => reparse(uris, opts)); + return; + } const cached_units = [], parsers = []; for (let docinfo of liveParsers.values()) { if (uris.includes(docinfo.uri)) { @@ -291,14 +293,16 @@ let hasDiagnosticRelatedInformationCapability = false; connection.onInitialize((params) => { - // the storage path is passed to us by the client side of the extension - const library_cache_path = (params.initializationOptions || {}).globalStoragePath; - trace(`library_cache_path: ${library_cache_path}`); - // the android library is loaded asynchronously, with the global `androidLibrary` variable // set to the promise while it is loading. - androidLibrary = loadAndroidSystemLibrary(library_cache_path) - .then(library => androidLibrary = library); + androidLibrary = loadAndroidSystemLibrary((params.initializationOptions || {}).extensionPath) + .then( + library => androidLibrary = library, + err => { + console.log(`Android library load failed: ${err.message}\n Code completion may not be available.`); + return new Map(); + } + ); let capabilities = params.capabilities;