From 12705999876afb5305ccef3f29296b3a5167e88c Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 3 Dec 2022 11:28:10 -0500 Subject: [PATCH] Tsify compiler finder (#4390) * Convert compiler-finder to ts * Updated CompilerProps getter typings to be way more awesome * A couple small tweaks * Address review comments --- ...{compiler-finder.js => compiler-finder.ts} | 76 ++++++++++++++----- lib/properties.ts | 59 +++++++++++--- 2 files changed, 103 insertions(+), 32 deletions(-) rename lib/{compiler-finder.js => compiler-finder.ts} (88%) diff --git a/lib/compiler-finder.js b/lib/compiler-finder.ts similarity index 88% rename from lib/compiler-finder.js rename to lib/compiler-finder.ts index 48f62d81de7..33284244119 100644 --- a/lib/compiler-finder.js +++ b/lib/compiler-finder.ts @@ -27,27 +27,56 @@ import https from 'https'; import path from 'path'; import {promisify} from 'util'; +import AWS from 'aws-sdk'; import fs from 'fs-extra'; import _ from 'underscore'; import urljoin from 'url-join'; +import {Language} from '../types/languages.interfaces'; + import {InstanceFetcher} from './aws'; +import {CompileHandler} from './handlers/compile'; import {logger} from './logger'; +import {ClientOptionsHandler} from './options-handler'; +import {CompilerProps, RawPropertiesGetter} from './properties'; +import {PropertyGetter, PropertyValue, Widen} from './properties.interfaces'; const sleep = promisify(setTimeout); +export type CompilerFinderArguments = { + rootDir: string; + env: string[]; + hostname: string[]; + port: number; + gitReleaseName: string; + releaseBuildNumber: string; + wantedLanguages: string | null; + doCache: boolean; + fetchCompilersFromRemote: boolean; + ensureNoCompilerClash: boolean; + suppressConsoleLog: boolean; +}; + /*** * Finds and initializes the compilers stored on the properties files */ export class CompilerFinder { - /*** - * @param {CompileHandler} compileHandler - * @param {CompilerProps} compilerProps - * @param {propsFor} awsProps - * @param {Object} args - * @param {Object} optionsHandler - */ - constructor(compileHandler, compilerProps, awsProps, args, optionsHandler) { + compilerProps: CompilerProps['get']; + ceProps: PropertyGetter; + awsProps: PropertyGetter; + args: CompilerFinderArguments; + compileHandler: CompileHandler; + languages: Record; + awsPoller: InstanceFetcher | null = null; + optionsHandler: ClientOptionsHandler; + + constructor( + compileHandler: CompileHandler, + compilerProps: CompilerProps, + awsProps: PropertyGetter, + args: CompilerFinderArguments, + optionsHandler: ClientOptionsHandler, + ) { this.compilerProps = compilerProps.get.bind(compilerProps); this.ceProps = compilerProps.ceProps; this.awsProps = awsProps; @@ -95,7 +124,7 @@ export class CompilerFinder { `${uriSchema}://${host}:${port}${apiPath}\n` + `Status Code: ${statusCode}`, ); - } else if (!/^application\/json/.test(contentType)) { + } else if (!contentType || !/^application\/json/.test(contentType)) { error = new Error( 'Invalid content-type.\n' + `Expected application/json but received ${contentType}`, @@ -129,7 +158,7 @@ export class CompilerFinder { return compiler; }); resolve(compilers); - } catch (e) { + } catch (e: any) { logger.error(`Error parsing response from ${uri} '${str}': ${e.message}`); reject(e); } @@ -154,7 +183,7 @@ export class CompilerFinder { logger.info('Fetching instances from AWS'); const instances = await this.awsInstances(); return Promise.all( - instances.map(instance => { + (instances.filter(instance => instance !== undefined) as AWS.EC2.Instance[]).map(instance => { logger.info('Checking instance ' + instance.InstanceId); const address = this.awsProps('externalTestMode', false) ? instance.PublicDnsName @@ -164,13 +193,16 @@ export class CompilerFinder { ); } - async compilerConfigFor(langId, compilerId, parentProps) { + async compilerConfigFor(langId: string, compilerId: string, parentProps: RawPropertiesGetter) { const base = `compiler.${compilerId}.`; - function props(propName, def) { + function props(propName: string, defaultValue: undefined): PropertyValue; + function props(propName: string, defaultValue: Widen): typeof defaultValue; + function props(propName: string, defaultValue?: unknown): T; + function props(propName: string, defaultValue?: unknown) { const propsForCompiler = parentProps(langId, base + propName); if (propsForCompiler !== undefined) return propsForCompiler; - return parentProps(langId, propName, def); + return parentProps(langId, propName, defaultValue); } const ceToolsPath = props('ceToolsPath', './'); @@ -206,7 +238,7 @@ export class CompilerFinder { if (envVarsString === '') { return []; } - const arr = []; + const arr: [string, string][] = []; for (const el of envVarsString.split(':')) { const [env, setting] = el.split('='); arr.push([env, setting]); @@ -261,7 +293,7 @@ export class CompilerFinder { notification: props('notification', ''), isSemVer: isSemVer, semver: semverVer, - libsArr: this.getSupportedLibrariesArr(props, langId), + libsArr: this.getSupportedLibrariesArr(props), tools: _.omit(this.optionsHandler.get().tools[langId], tool => tool.isCompilerExcluded(compilerId, props)), unwiseOptions: props('unwiseOptions', '').split('|'), hidden: props('hidden', false), @@ -328,15 +360,15 @@ export class CompilerFinder { } async getCompilers() { - const compilers = []; + const compilers: any[] = []; _.each(this.getExes(), (exs, langId) => { _.each(exs, exe => compilers.push(this.recurseGetCompilers(langId, exe, this.compilerProps))); }); return Promise.all(compilers); } - ensureDistinct(compilers) { - const ids = {}; + ensureDistinct(compilers: any[]) { + const ids: Record = {}; let foundClash = false; _.each(compilers, compiler => { if (!ids[compiler.id]) ids[compiler.id] = []; @@ -372,14 +404,16 @@ export class CompilerFinder { } getExes() { - const langToCompilers = this.compilerProps(this.languages, 'compilers', '', exs => _.compact(exs.split(':'))); + const langToCompilers = this.compilerProps(this.languages, 'compilers', '', exs => + _.compact((exs as string).split(':')), + ); this.addNdkExes(langToCompilers); logger.info('Exes found:', langToCompilers); return langToCompilers; } addNdkExes(langToCompilers) { - const ndkPaths = this.compilerProps(this.languages, 'androidNdk'); + const ndkPaths = this.compilerProps(this.languages, 'androidNdk') as unknown as Record; _.each(ndkPaths, (ndkPath, langId) => { if (ndkPath) { const toolchains = fs.readdirSync(`${ndkPath}/toolchains`); diff --git a/lib/properties.ts b/lib/properties.ts index 3075e2cdd4a..6dbb8c29900 100644 --- a/lib/properties.ts +++ b/lib/properties.ts @@ -67,6 +67,8 @@ export function get(base: string, property: string, defaultValue?: unknown): unk return result; } +export type RawPropertiesGetter = typeof get; + export function parseProperties(blob, name) { const props = {}; for (const [index, lineOrig] of blob.split('\n').entries()) { @@ -132,9 +134,9 @@ type LanguageDef = { * Compiler property fetcher */ export class CompilerProps { - private languages: Record; - private propsByLangId: Record; - private ceProps: any; + public readonly languages: Record; + public readonly propsByLangId: Record; + public readonly ceProps: PropertyGetter; /*** * Creates a CompilerProps lookup function @@ -149,6 +151,9 @@ export class CompilerProps { _.each(this.languages, lang => (this.propsByLangId[lang.id] = propsFor(lang.id))); } + $getInternal(base: string, property: string, defaultValue: undefined): PropertyValue; + $getInternal(base: string, property: string, defaultValue: Widen): typeof defaultValue; + $getInternal(base: string, property: string, defaultValue?: PropertyValue): T; $getInternal(langId: string, key: string, defaultValue: PropertyValue): PropertyValue { const languagePropertyValue = this.propsByLangId[langId](key); if (languagePropertyValue !== undefined) { @@ -171,26 +176,58 @@ export class CompilerProps { * @returns {*} Transformed value(s) found or fn(defaultValue) */ get( - langs: string | LanguageDef[], + base: string | LanguageDef[] | Record, + property: string, + defaultValue?: undefined, + fn?: undefined, + ): PropertyValue; + get( + base: string | LanguageDef[] | Record, + property: string, + defaultValue: Widen, + fn?: undefined, + ): typeof defaultValue; + get( + base: string | LanguageDef[] | Record, + property: string, + defaultValue?: PropertyValue, + fn?: undefined, + ): T; + + get( + base: string | LanguageDef[] | Record, + property: string, + defaultValue?: undefined, + fn?: (item: PropertyValue, language?: any) => R, + ): R; + get( + base: string | LanguageDef[] | Record, + property: string, + defaultValue: Widen, + fn?: (item: PropertyValue, language?: any) => R, + ): typeof defaultValue | R; + + get( + langs: string | LanguageDef[] | Record, key: string, - defaultValue: PropertyValue, - fn: (item: PropertyValue, language?: any) => PropertyValue = _.identity, + defaultValue?: PropertyValue, + fn?: (item: PropertyValue, language?: any) => unknown, ) { - fn = fn || _.identity; + const map_fn = fn || _.identity; if (_.isEmpty(langs)) { - return fn(this.ceProps(key, defaultValue)); + return map_fn(this.ceProps(key, defaultValue)); } if (!_.isString(langs)) { return _.chain(langs) - .map(lang => [lang.id, fn(this.$getInternal(lang.id, key, defaultValue), lang)]) + .map(lang => [lang.id, map_fn(this.$getInternal(lang.id, key, defaultValue), lang)]) .object() .value(); } else { if (this.propsByLangId[langs]) { - return fn(this.$getInternal(langs, key, defaultValue), this.languages[langs]); + return map_fn(this.$getInternal(langs, key, defaultValue), this.languages[langs]); } else { logger.error(`Tried to pass ${langs} as a language ID`); - return fn(defaultValue); + return map_fn(defaultValue); } } }