Skip to content

Commit

Permalink
Mg/props ts (#4340)
Browse files Browse the repository at this point in the history
* WIP

* Type changes to the property getter

* Finish getting types working for properties.ts

* Reduce type casts in exec.ts

* Format files

* Remove old comment

Co-authored-by: Matt Godbolt <matt@godbolt.org>
  • Loading branch information
jeremy-rifkin and mattgodbolt committed Nov 26, 2022
1 parent a208c2f commit e13fe4a
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 50 deletions.
4 changes: 2 additions & 2 deletions lib/base-compiler.ts
Expand Up @@ -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';
Expand All @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions lib/compilers/avrgcc6502.ts
Expand Up @@ -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<string>(`compiler.${this.compiler.id}.avrgccpath`);
this.xapath = this.compilerProps<string>(`compiler.${this.compiler.id}.xapath`);
this.avrlibstdcpppath = this.compilerProps<string>(`compiler.${this.compiler.id}.avrlibstdcpppath`);
this.outputFilebase = 'example';
}

Expand Down
2 changes: 1 addition & 1 deletion lib/compilers/clang.ts
Expand Up @@ -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<string | undefined>('llvmDisassembler');
}
}

Expand Down
4 changes: 2 additions & 2 deletions lib/compilers/dosbox-compiler.ts
Expand Up @@ -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<string>(`compiler.${this.compiler.id}.dosbox`);
this.root = this.compilerProps<string>(`compiler.${this.compiler.id}.root`);
this.asm = new TurboCAsmParser(this.compilerProps);
}

Expand Down
8 changes: 4 additions & 4 deletions lib/compilers/dotnet.ts
Expand Up @@ -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<string>(`compiler.${this.compiler.id}.targetFramework`);
this.buildConfig = this.compilerProps<string>(`compiler.${this.compiler.id}.buildConfig`);
this.clrBuildDir = this.compilerProps<string>(`compiler.${this.compiler.id}.clrDir`);
this.langVersion = this.compilerProps<string>(`compiler.${this.compiler.id}.langVersion`);
this.asm = new DotNetAsmParser();
}

Expand Down
6 changes: 3 additions & 3 deletions lib/compilers/golang.ts
Expand Up @@ -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<string | undefined>(`compiler.${this.compiler.id}.goroot`);
const goarch = this.compilerProps<string | undefined>(`compiler.${this.compiler.id}.goarch`);
const goos = this.compilerProps<string | undefined>(`compiler.${this.compiler.id}.goos`);

this.GOENV = {};
if (goroot) {
Expand Down
3 changes: 2 additions & 1 deletion lib/compilers/python.ts
Expand Up @@ -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<string>('disasmScript') ||
resolvePathFromAppRoot('etc', 'scripts', 'disasms', 'dis_all.py');
}

override processAsm(result) {
Expand Down
2 changes: 1 addition & 1 deletion lib/compilers/racket.ts
Expand Up @@ -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<string>(`compiler.${this.compiler.id}.raco`);
}

override optionsForFilter(filters: ParseFilters, outputFilename: string, userOptions?: string[]): string[] {
Expand Down
2 changes: 1 addition & 1 deletion lib/compilers/rga.ts
Expand Up @@ -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<string>(`compiler.${this.compiler.id}.dxcPath`);
logger.debug(`RGA compiler ${this.compiler.id} configured to use DXC at ${this.dxcPath}`);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/compilers/rust.ts
Expand Up @@ -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<string>('linker');
}

override getSharedLibraryPathsAsArguments(libraries, libDownloadPath) {
Expand Down
4 changes: 2 additions & 2 deletions lib/compilers/spirv.ts
Expand Up @@ -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<string>('translatorPath');
this.disassemblerPath = this.compilerProps<string>('disassemblerPath');
}

override prepareArguments(userOptions, filters, backendOptions, inputFilename, outputFilename, libraries) {
Expand Down
18 changes: 9 additions & 9 deletions lib/exec.ts
Expand Up @@ -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<string>(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}`);
Expand All @@ -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<string>(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}`);
Expand Down Expand Up @@ -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<string>('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<string>('nsjail'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform);
}

function withFirejailTimeout(args: string[], options?) {
Expand Down Expand Up @@ -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<string>('firejail'), jailingOptions.concat([`./${execName}`]).concat(args), options);
}

const sandboxDispatchTable = {
Expand Down Expand Up @@ -332,7 +332,7 @@ const wineSandboxName = 'ce-wineserver';
let wineInitPromise: Promise<void> | null;

export function startWineInit() {
const wine = execProps('wine');
const wine = execProps<string | undefined>('wine');
if (!wine) {
logger.info('WINE not configured');
return;
Expand All @@ -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<string>('firejail') : null;
const env = applyWineEnv({PATH: process.env.PATH});
const prefix = env.WINEPREFIX;

Expand Down Expand Up @@ -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<string>('wine'), args, options);
}

async function executeFirejail(command, args, options) {
options = _.clone(options) || {};
const firejail = execProps('firejail');
const firejail = execProps<string>('firejail');
const baseOptions = withFirejailTimeout(
['--quiet', '--deterministic-exit-code', '--deterministic-shutdown'],
options,
Expand Down
48 changes: 48 additions & 0 deletions 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> = 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<T extends PropertyValue>(property: string, defaultValue: Widen<T>): typeof defaultValue;
function superficialGetter<T extends PropertyValue>(property: string, defaultValue?: unknown): T;
function superficialGetter(property: string, defaultValue?: unknown): unknown {
return; // eslint-disable-line no-useless-return
}

export type PropertyGetter = typeof superficialGetter;
57 changes: 37 additions & 20 deletions lib/properties.js → lib/properties.ts
Expand Up @@ -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<string, Record<string, PropertyValue>> = {};

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<string, PropertyValue> {
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<T extends PropertyValue>(
base: string,
property: string,
defaultValue: Widen<T>,
): typeof defaultValue;
export function get<T extends PropertyValue>(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) {
Expand All @@ -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
Expand Down Expand Up @@ -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);
};
Expand All @@ -117,17 +124,22 @@ export function propsFor(base) {
// return funcB();
// }

type LanguageDef = {
id: string;
};

/***
* Compiler property fetcher
*/
export class CompilerProps {
private languages: Record<string, any>;
private propsByLangId: Record<string, PropertyGetter>;
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<string, LanguageDef>, ceProps: PropertyGetter) {
this.languages = languages;
this.propsByLangId = {};

Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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));
Expand All @@ -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<string, PropertyValue>): PropertyGetter {
return (prop, def) => (fake[prop] === undefined ? def : fake[prop]);
}

0 comments on commit e13fe4a

Please sign in to comment.