diff --git a/lib/base-compiler.ts b/lib/base-compiler.ts index e81b2306da4..ec02b260810 100644 --- a/lib/base-compiler.ts +++ b/lib/base-compiler.ts @@ -74,6 +74,7 @@ import {Packager} from './packager'; import {AsmParser} from './parsers/asm-parser'; import {IAsmParser} from './parsers/asm-parser.interfaces'; import {LlvmPassDumpParser} from './parsers/llvm-pass-dump-parser'; +import {PropertyGetter} from './properties.interfaces'; import {getToolchainPath} from './toolchain-utils'; import {Tool, ToolResult, ToolTypeKey} from './tooling/base-tool.interface'; import * as utils from './utils'; @@ -83,8 +84,7 @@ export class BaseCompiler { public lang: Language; protected compileFilename: string; protected env: any; - // Note that this can also return undefined and boolean | number, but we still need properties.js to ts - protected compilerProps: (key: string, defaultValue?: string) => string; + protected compilerProps: PropertyGetter; protected alwaysResetLdPath: any; protected delayCleanupTemp: any; protected stubRe: RegExp; diff --git a/lib/compilers/avrgcc6502.ts b/lib/compilers/avrgcc6502.ts index 53895f93d50..42234828848 100644 --- a/lib/compilers/avrgcc6502.ts +++ b/lib/compilers/avrgcc6502.ts @@ -38,9 +38,9 @@ export class AvrGcc6502Compiler extends BaseCompiler { constructor(compilerInfo, env) { super(compilerInfo, env); - this.avrgccpath = this.compilerProps(`compiler.${this.compiler.id}.avrgccpath`); - this.xapath = this.compilerProps(`compiler.${this.compiler.id}.xapath`); - this.avrlibstdcpppath = this.compilerProps(`compiler.${this.compiler.id}.avrlibstdcpppath`); + this.avrgccpath = this.compilerProps(`compiler.${this.compiler.id}.avrgccpath`); + this.xapath = this.compilerProps(`compiler.${this.compiler.id}.xapath`); + this.avrlibstdcpppath = this.compilerProps(`compiler.${this.compiler.id}.avrlibstdcpppath`); this.outputFilebase = 'example'; } diff --git a/lib/compilers/clang.ts b/lib/compilers/clang.ts index 791f32cdd3f..3343c841918 100644 --- a/lib/compilers/clang.ts +++ b/lib/compilers/clang.ts @@ -62,7 +62,7 @@ export class ClangCompiler extends BaseCompiler { if (fs.existsSync(llvmDisassemblerPath)) { this.llvmDisassemblerPath = llvmDisassemblerPath; } else { - this.llvmDisassemblerPath = this.compilerProps('llvmDisassembler'); + this.llvmDisassemblerPath = this.compilerProps('llvmDisassembler'); } } diff --git a/lib/compilers/dosbox-compiler.ts b/lib/compilers/dosbox-compiler.ts index 2d48d2c6246..8ece53ded7e 100644 --- a/lib/compilers/dosbox-compiler.ts +++ b/lib/compilers/dosbox-compiler.ts @@ -38,8 +38,8 @@ export class DosboxCompiler extends BaseCompiler { constructor(compilerInfo, env) { super(compilerInfo, env); - this.dosbox = this.compilerProps(`compiler.${this.compiler.id}.dosbox`); - this.root = this.compilerProps(`compiler.${this.compiler.id}.root`); + this.dosbox = this.compilerProps(`compiler.${this.compiler.id}.dosbox`); + this.root = this.compilerProps(`compiler.${this.compiler.id}.root`); this.asm = new TurboCAsmParser(this.compilerProps); } diff --git a/lib/compilers/dotnet.ts b/lib/compilers/dotnet.ts index d712b2b76ff..e7001427d0e 100644 --- a/lib/compilers/dotnet.ts +++ b/lib/compilers/dotnet.ts @@ -39,10 +39,10 @@ class DotNetCompiler extends BaseCompiler { constructor(compilerInfo, env) { super(compilerInfo, env); - this.targetFramework = this.compilerProps(`compiler.${this.compiler.id}.targetFramework`); - this.buildConfig = this.compilerProps(`compiler.${this.compiler.id}.buildConfig`); - this.clrBuildDir = this.compilerProps(`compiler.${this.compiler.id}.clrDir`); - this.langVersion = this.compilerProps(`compiler.${this.compiler.id}.langVersion`); + this.targetFramework = this.compilerProps(`compiler.${this.compiler.id}.targetFramework`); + this.buildConfig = this.compilerProps(`compiler.${this.compiler.id}.buildConfig`); + this.clrBuildDir = this.compilerProps(`compiler.${this.compiler.id}.clrDir`); + this.langVersion = this.compilerProps(`compiler.${this.compiler.id}.langVersion`); this.asm = new DotNetAsmParser(); } diff --git a/lib/compilers/golang.ts b/lib/compilers/golang.ts index 00ef80d6c4b..b1de6688472 100644 --- a/lib/compilers/golang.ts +++ b/lib/compilers/golang.ts @@ -57,9 +57,9 @@ export class GolangCompiler extends BaseCompiler { constructor(compilerInfo, env) { super(compilerInfo, env); - const goroot = this.compilerProps(`compiler.${this.compiler.id}.goroot`); - const goarch = this.compilerProps(`compiler.${this.compiler.id}.goarch`); - const goos = this.compilerProps(`compiler.${this.compiler.id}.goos`); + const goroot = this.compilerProps(`compiler.${this.compiler.id}.goroot`); + const goarch = this.compilerProps(`compiler.${this.compiler.id}.goarch`); + const goos = this.compilerProps(`compiler.${this.compiler.id}.goos`); this.GOENV = {}; if (goroot) { diff --git a/lib/compilers/python.ts b/lib/compilers/python.ts index a9a2f2c5ae0..1036f2222c9 100644 --- a/lib/compilers/python.ts +++ b/lib/compilers/python.ts @@ -40,7 +40,8 @@ export class PythonCompiler extends BaseCompiler { this.compiler.demangler = ''; this.demanglerClass = null; this.disasmScriptPath = - this.compilerProps('disasmScript') || resolvePathFromAppRoot('etc', 'scripts', 'disasms', 'dis_all.py'); + this.compilerProps('disasmScript') || + resolvePathFromAppRoot('etc', 'scripts', 'disasms', 'dis_all.py'); } override processAsm(result) { diff --git a/lib/compilers/racket.ts b/lib/compilers/racket.ts index d64b5d5a075..68bbec7572a 100644 --- a/lib/compilers/racket.ts +++ b/lib/compilers/racket.ts @@ -42,7 +42,7 @@ export class RacketCompiler extends BaseCompiler { info.disabledFilters = ['labels', 'directives', 'commentOnly', 'trim']; } super(info, env); - this.raco = this.compilerProps(`compiler.${this.compiler.id}.raco`); + this.raco = this.compilerProps(`compiler.${this.compiler.id}.raco`); } override optionsForFilter(filters: ParseFilters, outputFilename: string, userOptions?: string[]): string[] { diff --git a/lib/compilers/rga.ts b/lib/compilers/rga.ts index 36ffcc2062b..050e7499c34 100644 --- a/lib/compilers/rga.ts +++ b/lib/compilers/rga.ts @@ -50,7 +50,7 @@ export class RGACompiler extends BaseCompiler { super(info, env); this.compiler.supportsIntel = false; - this.dxcPath = this.compilerProps(`compiler.${this.compiler.id}.dxcPath`); + this.dxcPath = this.compilerProps(`compiler.${this.compiler.id}.dxcPath`); logger.debug(`RGA compiler ${this.compiler.id} configured to use DXC at ${this.dxcPath}`); } diff --git a/lib/compilers/rust.ts b/lib/compilers/rust.ts index bde2109c0b2..28345e335f0 100644 --- a/lib/compilers/rust.ts +++ b/lib/compilers/rust.ts @@ -58,7 +58,7 @@ export class RustCompiler extends BaseCompiler { this.compiler.llvmOptArg = ['-C', 'llvm-args=-print-after-all -print-before-all']; this.compiler.llvmOptModuleScopeArg = ['-C', 'llvm-args=-print-module-scope']; this.compiler.llvmOptNoDiscardValueNamesArg = isNightly ? ['-Z', 'fewer-names=no'] : []; - this.linker = this.compilerProps('linker'); + this.linker = this.compilerProps('linker'); } override getSharedLibraryPathsAsArguments(libraries, libDownloadPath) { diff --git a/lib/compilers/spirv.ts b/lib/compilers/spirv.ts index 14c00e6d00a..b94fd638d69 100644 --- a/lib/compilers/spirv.ts +++ b/lib/compilers/spirv.ts @@ -44,8 +44,8 @@ export class SPIRVCompiler extends BaseCompiler { this.asm = new SPIRVAsmParser(this.compilerProps); - this.translatorPath = this.compilerProps('translatorPath'); - this.disassemblerPath = this.compilerProps('disassemblerPath'); + this.translatorPath = this.compilerProps('translatorPath'); + this.disassemblerPath = this.compilerProps('disassemblerPath'); } override prepareArguments(userOptions, filters, backendOptions, inputFilename, outputFilename, libraries) { diff --git a/lib/exec.ts b/lib/exec.ts index 777af64394f..1f1ad5cf94b 100644 --- a/lib/exec.ts +++ b/lib/exec.ts @@ -170,7 +170,7 @@ export function executeDirect( export function getNsJailCfgFilePath(configName: string): string { const propKey = `nsjail.config.${configName}`; - const configPath = execProps(propKey); + const configPath = execProps(propKey); if (configPath === undefined) { logger.error(`Could not find ${propKey}. Are you missing a definition?`); throw new Error(`Missing nsjail execution config property key ${propKey}`); @@ -180,7 +180,7 @@ export function getNsJailCfgFilePath(configName: string): string { export function getFirejailProfileFilePath(profileName: string): string { const propKey = `firejail.profile.${profileName}`; - const profilePath = execProps(propKey); + const profilePath = execProps(propKey); if (profilePath === undefined) { logger.error(`Could not find ${propKey}. Are you missing a definition?`); throw new Error(`Missing firejail execution profile property key ${propKey}`); @@ -259,12 +259,12 @@ export function getSandboxNsjailOptions(command: string, args: string[], options function sandboxNsjail(command, args, options) { logger.info('Sandbox execution via nsjail', {command, args}); const nsOpts = getSandboxNsjailOptions(command, args, options); - return executeDirect(execProps('nsjail'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform); + return executeDirect(execProps('nsjail'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform); } function executeNsjail(command, args, options) { const nsOpts = getNsJailOptions('execute', command, args, options); - return executeDirect(execProps('nsjail'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform); + return executeDirect(execProps('nsjail'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform); } function withFirejailTimeout(args: string[], options?) { @@ -299,7 +299,7 @@ function sandboxFirejail(command: string, args: string[], options) { } delete options.env; - return executeDirect(execProps('firejail'), jailingOptions.concat([`./${execName}`]).concat(args), options); + return executeDirect(execProps('firejail'), jailingOptions.concat([`./${execName}`]).concat(args), options); } const sandboxDispatchTable = { @@ -332,7 +332,7 @@ const wineSandboxName = 'ce-wineserver'; let wineInitPromise: Promise | null; export function startWineInit() { - const wine = execProps('wine'); + const wine = execProps('wine'); if (!wine) { logger.info('WINE not configured'); return; @@ -341,7 +341,7 @@ export function startWineInit() { const server = execProps('wineServer'); const executionType = execProps('executionType', 'none'); // We need to fire up a firejail wine server even in nsjail world (for now). - const firejail = executionType === 'firejail' || executionType === 'nsjail' ? execProps('firejail') : null; + const firejail = executionType === 'firejail' || executionType === 'nsjail' ? execProps('firejail') : null; const env = applyWineEnv({PATH: process.env.PATH}); const prefix = env.WINEPREFIX; @@ -466,12 +466,12 @@ async function executeWineDirect(command, args, options) { options.env = applyWineEnv(options.env); args = [command, ...args]; await wineInitPromise; - return await executeDirect(execProps('wine'), args, options); + return await executeDirect(execProps('wine'), args, options); } async function executeFirejail(command, args, options) { options = _.clone(options) || {}; - const firejail = execProps('firejail'); + const firejail = execProps('firejail'); const baseOptions = withFirejailTimeout( ['--quiet', '--deterministic-exit-code', '--deterministic-shutdown'], options, diff --git a/lib/properties.interfaces.ts b/lib/properties.interfaces.ts new file mode 100644 index 00000000000..d90ccc093c3 --- /dev/null +++ b/lib/properties.interfaces.ts @@ -0,0 +1,48 @@ +// Copyright (c) 2022, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +export type PropertyValue = string | boolean | number | undefined; + +// names don't matter +interface TypeMap { + a: string; + b: boolean; + c: number; + d: undefined; +} + +export type Widen = T extends T + ? { + [P in keyof TypeMap]: T extends TypeMap[P] ? TypeMap[P] : never; + }[keyof TypeMap] + : T; + +function superficialGetter(property: string, defaultValue?: undefined): PropertyValue; +function superficialGetter(property: string, defaultValue: Widen): typeof defaultValue; +function superficialGetter(property: string, defaultValue?: unknown): T; +function superficialGetter(property: string, defaultValue?: unknown): unknown { + return; // eslint-disable-line no-useless-return +} + +export type PropertyGetter = typeof superficialGetter; diff --git a/lib/properties.js b/lib/properties.ts similarity index 77% rename from lib/properties.js rename to lib/properties.ts index 0d2f234764a..3075e2cdd4a 100644 --- a/lib/properties.js +++ b/lib/properties.ts @@ -28,24 +28,31 @@ import path from 'path'; import _ from 'underscore'; import {logger} from './logger'; +import {PropertyGetter, PropertyValue, Widen} from './properties.interfaces'; import {toProperty} from './utils'; -let properties = {}; +let properties: Record> = {}; -let hierarchy = []; +let hierarchy: string[] = []; let propDebug = false; -function findProps(base, elem) { - const name = base + '.' + elem; - return properties[name]; +function findProps(base: string, elem: string): Record { + return properties[`${base}.${elem}`]; } function debug(string) { if (propDebug) logger.info(`prop: ${string}`); } -export function get(base, property, defaultValue) { +export function get(base: string, property: string, defaultValue: undefined): PropertyValue; +export function get( + base: string, + property: string, + defaultValue: Widen, +): typeof defaultValue; +export function get(base: string, property: string, defaultValue?: unknown): T; +export function get(base: string, property: string, defaultValue?: unknown): unknown { let result = defaultValue; let source = 'default'; for (const elem of hierarchy) { @@ -62,15 +69,15 @@ export function get(base, property, defaultValue) { export function parseProperties(blob, name) { const props = {}; - for (let [index, line] of blob.split('\n').entries()) { - line = line.replace(/#.*/, '').trim(); + for (const [index, lineOrig] of blob.split('\n').entries()) { + const line = lineOrig.replace(/#.*/, '').trim(); if (!line) continue; - let split = line.match(/([^=]+)=(.*)/); + const split = line.match(/([^=]+)=(.*)/); if (!split) { logger.error(`Bad line: ${line} in ${name}: ${index + 1}`); continue; } - let prop = split[1].trim(); + const prop = split[1].trim(); let val = split[2].trim(); // hack to avoid applying toProperty to version properties // so that they're not parsed as numbers @@ -105,7 +112,7 @@ export function reset() { logger.debug('Properties reset'); } -export function propsFor(base) { +export function propsFor(base): PropertyGetter { return function (property, defaultValue) { return get(base, property, defaultValue); }; @@ -117,17 +124,22 @@ export function propsFor(base) { // return funcB(); // } +type LanguageDef = { + id: string; +}; + /*** * Compiler property fetcher */ export class CompilerProps { + private languages: Record; + private propsByLangId: Record; + private ceProps: any; + /*** * Creates a CompilerProps lookup function - * - * @param {CELanguages} languages - Supported languages - * @param {function} ceProps - propsFor function to get Compiler Explorer values from */ - constructor(languages, ceProps) { + constructor(languages: Record, ceProps: PropertyGetter) { this.languages = languages; this.propsByLangId = {}; @@ -137,7 +149,7 @@ export class CompilerProps { _.each(this.languages, lang => (this.propsByLangId[lang.id] = propsFor(lang.id))); } - $getInternal(langId, key, defaultValue) { + $getInternal(langId: string, key: string, defaultValue: PropertyValue): PropertyValue { const languagePropertyValue = this.propsByLangId[langId](key); if (languagePropertyValue !== undefined) { return languagePropertyValue; @@ -148,7 +160,7 @@ export class CompilerProps { /*** * Gets a value for a given key associated to the given languages from the properties * - * @param {?(string|CELanguages)} langs - Which langs to search in + * @param langs - Which langs to search in * For compatibility, {null} means looking into the Compiler Explorer properties (Not on any language) * If langs is a {string}, it refers to the language id we want to search into * If langs is a {CELanguages}, it refers to which languages we want to search into @@ -158,7 +170,12 @@ export class CompilerProps { * @param {?function} fn - Transformation to give to each value found * @returns {*} Transformed value(s) found or fn(defaultValue) */ - get(langs, key, defaultValue, fn = _.identity) { + get( + langs: string | LanguageDef[], + key: string, + defaultValue: PropertyValue, + fn: (item: PropertyValue, language?: any) => PropertyValue = _.identity, + ) { fn = fn || _.identity; if (_.isEmpty(langs)) { return fn(this.ceProps(key, defaultValue)); @@ -179,10 +196,10 @@ export class CompilerProps { } } -export function setDebug(debug) { +export function setDebug(debug: boolean) { propDebug = debug; } -export function fakeProps(fake) { +export function fakeProps(fake: Record): PropertyGetter { return (prop, def) => (fake[prop] === undefined ? def : fake[prop]); }