Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing Electron runner #5633

Merged
merged 63 commits into from
Feb 19, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
54e933d
Refactoring config options
wdanilo Jan 29, 2023
cd8a27d
Refactoring opts parsing in electron app
wdanilo Jan 29, 2023
cb85fae
Refactoring
wdanilo Jan 29, 2023
9baa8a8
refactoring
wdanilo Jan 30, 2023
d24bcb8
Refactoring
wdanilo Jan 30, 2023
e8ffe42
Making nested config work
wdanilo Jan 31, 2023
3f105e8
Refactoring
wdanilo Feb 1, 2023
130bfd2
Refactoring
wdanilo Feb 2, 2023
35e4223
Revert "Refactoring"
wdanilo Feb 2, 2023
3c0037f
Refactoring
wdanilo Feb 2, 2023
2a3a2fc
Refactoring
wdanilo Feb 2, 2023
ccdda51
Making it work again
wdanilo Feb 2, 2023
defe1f0
Refactoring
wdanilo Feb 4, 2023
1670acd
Refactoring
wdanilo Feb 4, 2023
cd86f7f
refactoring
wdanilo Feb 4, 2023
18f02e3
Making content typechecking in TS
wdanilo Feb 5, 2023
a113539
Making electron compile again
wdanilo Feb 5, 2023
0675ca1
Refactoring
wdanilo Feb 6, 2023
b34caa5
Refactoring
wdanilo Feb 8, 2023
8098bde
Refactoring
wdanilo Feb 9, 2023
45b772b
Refactoring
wdanilo Feb 9, 2023
db1f35e
Refactoring
wdanilo Feb 9, 2023
607ed24
Refactoring
wdanilo Feb 9, 2023
0a8d057
Refactoring
wdanilo Feb 9, 2023
4595393
Refactoring
wdanilo Feb 9, 2023
a09ad58
Refactoring
wdanilo Feb 11, 2023
bf832de
Refactoring
wdanilo Feb 11, 2023
68d19c7
Refactoring
wdanilo Feb 11, 2023
2011ece
Refactoring
wdanilo Feb 12, 2023
26ec66a
Adding Chrome options
wdanilo Feb 12, 2023
0263406
Refactoring
wdanilo Feb 12, 2023
0685854
Refactoring
wdanilo Feb 12, 2023
e9d27e2
Refactoring, applying review
wdanilo Feb 14, 2023
2be4549
Refactoring
wdanilo Feb 14, 2023
e5a8eb2
Refactoring, linting
wdanilo Feb 15, 2023
b954b66
Refactoring, applying review comments
wdanilo Feb 15, 2023
40018a1
Refactoring, extracting app configs to json files
wdanilo Feb 15, 2023
871098f
Merge branch 'develop' into wip/wdanilo/shader-compilation-improvemen…
wdanilo Feb 15, 2023
8830e95
Removing old files
wdanilo Feb 15, 2023
cbba1ed
Refactoring
wdanilo Feb 15, 2023
1776bc5
Refactoring
wdanilo Feb 15, 2023
26c81d6
Refactoring
wdanilo Feb 15, 2023
1122556
Refactoring
wdanilo Feb 15, 2023
fcd691f
Connecting new arg parser to Rust
wdanilo Feb 17, 2023
8e65da3
Refactoring
wdanilo Feb 17, 2023
d7ca350
Merge branch 'develop' into wip/wdanilo/shader-compilation-improvemen…
wdanilo Feb 17, 2023
bdba0e3
Linting
wdanilo Feb 17, 2023
2a57a59
Cleaning the code
wdanilo Feb 17, 2023
bc7a223
Applying review
wdanilo Feb 17, 2023
a5ebcf3
Merge branch 'develop' into wip/wdanilo/shader-compilation-improvemen…
wdanilo Feb 17, 2023
a97ac54
Fixing build
wdanilo Feb 17, 2023
2f5f4d9
Refactoring
wdanilo Feb 17, 2023
9ebbd3c
fixes #5664
wdanilo Feb 18, 2023
c2a42d2
Merge branch 'develop' into wip/wdanilo/shader-compilation-improvemen…
wdanilo Feb 18, 2023
a5812f0
Refactoring
wdanilo Feb 18, 2023
0ed3f8a
Improving json reading macro so it is re-evaluated when json files ch…
wdanilo Feb 18, 2023
130aea4
Attempt to fix builds of ide with non-local gui source.
mwu-tow Feb 18, 2023
1c29315
Merge remote-tracking branch 'origin/wip/wdanilo/shader-compilation-i…
mwu-tow Feb 18, 2023
9210aaa
one more take
mwu-tow Feb 18, 2023
25da309
Merge branch 'develop' into wip/wdanilo/shader-compilation-improvemen…
wdanilo Feb 18, 2023
a5924e4
another take
mwu-tow Feb 18, 2023
ce683f4
Merge remote-tracking branch 'origin/wip/wdanilo/shader-compilation-i…
mwu-tow Feb 18, 2023
2f69bf3
Linting
wdanilo Feb 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactoring
  • Loading branch information
wdanilo committed Feb 11, 2023
commit a09ad58a05f52029a6143f61372c812b1940ae7c
240 changes: 240 additions & 0 deletions app/ide-desktop/lib/client/src/arg-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import chalk from 'chalk'
import buildCfg from '../../../build.json'
import * as config from './config'
const yargs = require('yargs')
import * as naming from './naming'
import stringLength from 'string-length'
import { hideBin } from 'yargs/helpers'

// ============
// === Help ===
// ============

const FULL_HELP_OPTION = 'full-help'

let usage = chalk.bold(
`
Enso ${buildCfg.version} command line interface.
Usage: enso [options] [--] [backend args]`
)

class Section {
entries: any[] = []
description = ''
constructor(entries: any[] = []) {
this.entries = entries
}
}

/** We use custom help printer because Yargs has many issues:
* 1. The option ordering is random and there is no way to enforce it.
* 2. The option groups ordering is random and there is no way to enforce it.
* 3. Every option has a `[type`] annotation and there is no API to disable it.
* 4. There is no option to print commands with single dash instead of double-dash.
* 5. Help coloring is not supported, and they do not want to support it:
* https://github.com/yargs/yargs/issues/251
*/
function printHelp(cfg: {
args: config.Args
groupsOrdering: string[]
secondaryGroups: string[]
fullHelp: boolean
}) {
console.log(usage)
const terminalWidth = yargs.terminalWidth()
const indentSize = 0
const optionPrefix = '-'
const spacing = 2
const sections: { [key: string]: Section } = {}
for (const groupName of cfg.groupsOrdering) {
if (cfg.fullHelp || !cfg.secondaryGroups.includes(groupName)) {
sections[groupName] = new Section()
}
}
let maxOptionLength = 0

for (const [groupName, group] of Object.entries(cfg.args.groups)) {
let section = sections[groupName]
if (section == null) {
section = new Section()
sections[groupName] = section
}
section.description = group.description
for (const option of group.optionsRecursive()) {
const cmdOption = naming.camelToKebabCase(option.qualifiedName())
maxOptionLength = Math.max(maxOptionLength, stringLength(cmdOption))
const entry = [cmdOption, option]
section.entries.push(entry)
}
}

for (const [optionName, option] of Object.entries(cfg.args.options)) {
const cmdOption = naming.camelToKebabCase(optionName)
maxOptionLength = Math.max(maxOptionLength, stringLength(cmdOption))
const entry = [cmdOption, option]
const section = sections[option.name]
if (section != null) {
section.entries.unshift(entry)
}
// sections['global'].entries.push(entry)
}

const borderStyle = (s: string) => chalk.gray(chalk.bold(s))

const leftWidth = maxOptionLength + indentSize + stringLength(optionPrefix) + spacing
const rightWidth = terminalWidth - leftWidth

for (const [groupName, section] of Object.entries(sections)) {
console.log('\n\n')
const groupTitle = chalk.bold(`${naming.camelCaseToTitle(groupName)} Options `)
console.log(groupTitle)
const description = wordWrap(section.description, terminalWidth).join('\n')
console.log(description)
console.log()
for (const [cmdOption, option] of section.entries) {
if (cfg.fullHelp || option.primary) {
const indent = ' '.repeat(indentSize)
let left = indent + chalk.bold(chalk.green(optionPrefix + cmdOption))
const spaces = ' '.repeat(leftWidth - stringLength(left))
left = left + spaces

let firstSentenceSplit = option.description.indexOf('. ')
let firstSentence =
firstSentenceSplit == -1
? option.description
: option.description.slice(0, firstSentenceSplit + 1)
let otherSentences = option.description.slice(firstSentence.length)

const def = option.defaultDescription ?? option.default
let defaults = ''
if (def != null && def !== '') {
defaults = ` Defaults to ${chalk.green(def)}.`
}
let description = firstSentence + defaults + chalk.gray(otherSentences)
const lines = wordWrap(description, rightWidth).map(
line => line + ' '.repeat(rightWidth - stringLength(line))
)
const right = lines.join('\n' + ' '.repeat(leftWidth))
console.log(left + right)
}
}
}
}

function wordWrap(str: string, width: number): string[] {
if (width <= 0) {
return []
}
let line = ''
const lines = []
const inputLines = str.split('\n')
for (const inputLine of inputLines) {
for (let word of inputLine.split(' ')) {
if (stringLength(word) > width) {
if (line.length > 0) {
lines.push(line)
line = ''
}
const wordChunks = []
while (stringLength(word) > width) {
wordChunks.push(word.slice(0, width))
word = word.slice(width)
}
wordChunks.push(word)
for (const wordChunk of wordChunks) {
lines.push(wordChunk)
}
} else {
if (stringLength(line) + stringLength(word) >= width) {
lines.push(line)
line = ''
}
if (line.length != 0) {
line += ' '
}
line += word
}
}
}
if (line) {
lines.push(line)
}
return lines
}

// =====================
// === Option Parser ===
// =====================

export function parseArgs() {
const args = config.my_args
let argv = hideBin(process.argv)

const yargOptions = args.optionsRecursive().reduce((opts: { [key: string]: any }, option) => {
const yargsParam = Object.assign({}, option)
// @ts-ignore
yargsParam.requiresArg = ['string', 'array'].includes(yargsParam.type)
// @ts-ignore
yargsParam.default = undefined
// @ts-ignore
opts[naming.camelToKebabCase(option.qualifiedName())] = yargsParam
return opts
}, {})

let optParser = yargs()
.version(false)
.parserConfiguration({
'short-option-groups': false,
'dot-notation': false,
// Makes all flags passed after '--' be one string.
'populate--': true,
})
.strict()
.options(yargOptions)

// === Parsing ===

let xargs = optParser.parse(argv, {}, (err: any, argsDict: any, help: string) => {
console.log('!!!', err, help)
if (help) {
printHelp({
args,
groupsOrdering: [],
secondaryGroups: ['Electron Options'],
fullHelp: argsDict[FULL_HELP_OPTION],
})
process.exit()
}
})

for (const option of args.optionsRecursive()) {
const arg = xargs[naming.camelToKebabCase(option.qualifiedName())]
if (arg != null) {
option.value = arg
option.setByUser = true
}
}

let windowSize = config.WindowSize.default()
const parsedWindowSize = config.WindowSize.parse(args.groups.window.options.size.value)

if (parsedWindowSize instanceof Error) {
throw 'wrong window size'
} else {
windowSize = parsedWindowSize
}

if (args.options.help.value || args.options.fullHelp.value) {
printHelp({
args,
groupsOrdering: [],
secondaryGroups: ['Electron Options'],
fullHelp: args.options.fullHelp.value,
})
process.exit()
}

const backendOptions = xargs['--']

return { args, windowSize, backendOptions }
}
60 changes: 60 additions & 0 deletions app/ide-desktop/lib/client/src/bin/project-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import child_process, { SpawnOptions } from 'child_process'
import * as config from 'config'
import fss from 'node:fs'
import { logger } from '../../../content/src/config'
import util from 'node:util'
const execFile = util.promisify(child_process.execFile)

// =======================
// === Project Manager ===
// =======================

/** Return the Project Manager path if it is valid. Otherwise, report an error and close the
* application. */
export function pathOrPanic(args: config.Args): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a matter of taste but I don't really like sthOrPanic style of functions, I'd rather throw an exception and allow caller to decide how it should be handled. (And also allow to provide more context information to be logged along the way.)

Also, it is not clear to me why "file not existing" is a panic, while other spawning issue "like file missing appropriate permission" is an exception?

let binPath = args.groups.engine.options.projectManagerPath.value
let binExists = fss.existsSync(binPath)
if (!binExists) {
logger.error(`Could not find the project manager binary at ${binPath}.`)
process.exit(1)
}
return binPath
}

/** Executes the Project Manager with given arguments. */
async function exec(args: config.Args, processArgs: string[]) {
let binPath = pathOrPanic(args)
return await execFile(binPath, processArgs).catch(function (err) {
throw err
})
}

/** Spawn Project Manager process.
*
* The standard output and error handles will be inherited, i.e. will be redirected to the
* electron's app output and error handles. Input is piped to this process, so it will not be
* closed, until this process finished. */
export function spawn(args: config.Args, processArgs: string[]) {
return logger.groupMeasured(
`Starting the backend process with the following options: ${processArgs}`,
() => {
const binPath = pathOrPanic(args)
const stdin = 'pipe' as const
const stdout = 'inherit' as const
const stderr = 'inherit' as const
const stdio = [stdin, stdout, stderr]
const out = child_process.spawn(binPath, processArgs, { stdio })
logger.log(`Project Manager has been spawned (pid = ${out.pid}).`)
out.on('exit', code => {
logger.log(`Project Manager exited with code ${code}.`)
})
return out
}
)
}

export async function version(args: config.Args) {
if (args.groups.engine.options.backend.value) {
return await exec(args, ['--version']).then(t => t.stdout)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as fs from 'fs'
import * as mime from 'mime-types'
import * as path from 'path'
import * as portfinder from 'portfinder'
import { logger } from '../../content/src/config'
import { logger } from '../../../content/src/config'

// ==============
// === Config ===
Expand Down
11 changes: 7 additions & 4 deletions app/ide-desktop/lib/client/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as content from '../../content/src/config'
import * as paths from './paths'

// ==================
// === WindowSize ===
Expand Down Expand Up @@ -38,7 +39,7 @@ export class WindowSize {
// === Config ===
// ==============

export const options = content.options.merge(
export const my_args = content.options.merge(
new content.Group({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment: language style of descriptions is inconsistent ("do" vs. "does").

options: {
window: new content.Option({
Expand Down Expand Up @@ -135,7 +136,7 @@ export const options = content.options.merge(
description: 'Start the backend process.',
}),
projectManagerPath: new content.Option({
default: '',
default: paths.projectManager,
description:
'Set the path of a local project manager to use for running projects',
}),
Expand Down Expand Up @@ -353,5 +354,7 @@ export const options = content.options.merge(
},
})
)
options.groups.startup.options.platform.default = process.platform
options.groups.startup.options.platform.value = process.platform
my_args.groups.startup.options.platform.default = process.platform
my_args.groups.startup.options.platform.value = process.platform

export type Args = typeof my_args
36 changes: 36 additions & 0 deletions app/ide-desktop/lib/client/src/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import buildCfg from '../../../build.json'

export const versionInfo = {
version: buildCfg.version,
build: buildCfg.commit,
electron: process.versions.electron,
chrome: process.versions.chrome,
}

async function getDebugInfo() {
let procMemInfo = await process.getProcessMemoryInfo()
return {
version: versionInfo,
creation: process.getCreationTime(),
perf: {
cpu: process.getCPUUsage(),
},
memory: {
heap: process.getHeapStatistics(),
blink: process.getBlinkMemoryInfo(),
process: procMemInfo,
system: process.getSystemMemoryInfo(),
},
system: {
platform: process.platform,
arch: process.arch,
version: process.getSystemVersion(),
},
}
}

export async function printDebugInfo() {
let info = await getDebugInfo()
console.log(JSON.stringify(info, undefined, 4))
process.exit()
}
Loading