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

Improve linux dependency check #195

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/extensions/setup-extension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { XSDevToolbox } from '../types'
import setupMac from '../toolbox/setup/mac'
import setupLinux from '../toolbox/setup/linux'
import setupLinux from '../toolbox/setup/lin'
import setupWindows from '../toolbox/setup/windows'
import setupESP8266 from '../toolbox/setup/esp8266'
import setupESP32 from '../toolbox/setup/esp32'
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/update-extension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { XSDevToolbox } from '../types'
import updateMac from '../toolbox/update/mac'
import updateLinux from '../toolbox/update/linux'
import updateLinux from '../toolbox/update/lin'
import updateWindows from '../toolbox/update/windows'
import updateESP8266 from '../toolbox/update/esp8266'
import updateESP32 from '../toolbox/update/esp32'
Expand Down
4 changes: 2 additions & 2 deletions src/toolbox/prompt/devices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const DEVICE_ALIAS: Record<Device | 'esp8266', Device> = Object.freeze({
mac: 'mac',
windows_nt: 'win',
win: 'win',
linux: 'linux',
lin: 'linux',
linux: 'lin',
lin: 'lin',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I changed this in #190, it actually broke some of the later steps during setup that I hadn't anticipated. It turns out, what needed to change was the actual platform file name, and I did so in one of these commits.

esp: 'esp',
esp32: 'esp32',
wasm: 'wasm',
Expand Down
30 changes: 24 additions & 6 deletions src/toolbox/setup/esp32/linux.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import type { GluegunPrint } from 'gluegun'
import { execWithSudo } from '../../system/exec'
import type { Dependency } from '../../system/types'
import { findMissingDependencies, installPackages } from '../../system/packages'

// apt-get install git wget flex bison gperf python-is-python3 python3-pip python3-serial python-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util
export async function installDeps(
spinner: ReturnType<GluegunPrint['spin']>,
): Promise<void> {
await execWithSudo(
'apt-get install --yes git wget flex bison gperf python-is-python3 python3-pip python3-serial python3-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util',
{ stdout: process.stdout },
)
const dependencies: Dependency[] = [
{ name: 'bison', packageName: 'bison', type: 'binary' },
{ name: 'ccache', packageName: 'ccache', type: 'binary' },
{ name: 'cmake', packageName: 'cmake', type: 'binary' },
{ name: 'dfu-util', packageName: 'dfu-util', type: 'binary' },
{ name: 'flex', packageName: 'flex', type: 'binary' },
{ name: 'git', packageName: 'git', type: 'binary' },
{ name: 'gperf', packageName: 'gperf', type: 'binary' },
{ name: 'libffi', packageName: 'libffi-dev', type: 'library' },
{ name: 'libssl', packageName: 'libssl-dev', type: 'library' },
{ name: 'ninja', packageName: 'ninja-build', type: 'binary' },
{ name: 'pip', packageName: 'python-pip', type: 'binary' },
{ name: 'pyserial-miniterm', packageName: 'python3-serial', type: 'binary' },
{ name: 'python', packageName: 'python-is-python3', type: 'binary' },
{ name: 'setuptools', packageName: 'python3-setuptools', type: 'pylib' },
{ name: 'wget', packageName: 'wget', type: 'binary' },
]

const missingDependencies = await findMissingDependencies(dependencies)
if (missingDependencies.length !== 0) {
await installPackages(missingDependencies)
}
spinner.succeed()
}
17 changes: 12 additions & 5 deletions src/toolbox/setup/esp8266/linux.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import type { GluegunPrint } from 'gluegun'
import { execWithSudo } from '../../system/exec'
import type { Dependency } from '../../system/types'
import { findMissingDependencies, installPackages } from '../../system/packages'

export async function installDeps(
spinner: ReturnType<GluegunPrint['spin']>,
): Promise<void> {
spinner.start('Installing python deps with apt-get')
await execWithSudo(
'apt-get install --yes python-is-python3 python3-pip python3-serial',
{ stdout: process.stdout },
)
const dependencies: Dependency[] = [
{ name: 'pip', packageName: 'python-pip', type: 'binary' },
{ name: 'pyserial-miniterm', packageName: 'python3-serial', type: 'binary' },
{ name: 'python', packageName: 'python-is-python3', type: 'binary' },
]

const missingDependencies = await findMissingDependencies(dependencies)
if (missingDependencies.length !== 0) {
await installPackages(missingDependencies)
}
spinner.succeed()
}
43 changes: 23 additions & 20 deletions src/toolbox/setup/linux.ts → src/toolbox/setup/lin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ import os from 'os'
import { promisify } from 'util'
import { chmod } from 'fs'
import { filesystem, print, system } from 'gluegun'
import {
INSTALL_DIR,
INSTALL_PATH,
EXPORTS_FILE_PATH,
XSBUG_LOG_PATH,
} from './constants'
import { INSTALL_DIR, INSTALL_PATH, EXPORTS_FILE_PATH, XSBUG_LOG_PATH } from './constants'
import upsert from '../patching/upsert'
import { execWithSudo } from '../system/exec'
import { findMissingDependencies, installPackages } from '../system/packages'
import type { Dependency } from '../system/types'
import type { PlatformSetupArgs } from './types'
import { fetchLatestRelease, downloadReleaseTools } from './moddable'

Expand Down Expand Up @@ -45,24 +42,29 @@ export default async function ({
const spinner = print.spin()
spinner.start('Beginning setup...')

// 0. clone moddable repo into ./local/share directory if it does not exist yet
filesystem.dir(INSTALL_DIR)

await upsert(EXPORTS_FILE_PATH, `# Generated by xs-dev CLI`)
// 0. check for the required build tools and libraries
const dependencies: Dependency[] = [
{ name: 'bison', packageName: 'bison', type: 'binary' },
{ name: 'flex', packageName: 'flex', type: 'binary' },
{ name: 'gcc', packageName: 'gcc', type: 'binary' },
{ name: 'git', packageName: 'git', type: 'binary' },
{ name: 'gperf', packageName: 'gperf', type: 'binary' },
{ name: 'make', packageName: 'make', type: 'binary' },
{ name: 'wget', packageName: 'wget', type: 'binary' },
{ name: 'ncurses', packageName: 'libncurses-dev', type: 'library' },
{ name: 'gtk+-3.0', packageName: 'libgtk-3-dev', type: 'library' }
]

spinner.start('Checking for missing dependencies...')
const missingDependencies = await findMissingDependencies(dependencies)

// 1. Install or update the packages required to compile:
spinner.start('Installing dependencies...')
await execWithSudo(
'apt-get install --yes gcc git wget make libncurses-dev flex bison gperf',
{ stdout: process.stdout },
)
spinner.succeed()

// 2. Install the development version of the GTK+ 3 library
spinner.start('Installing GTK+ 3...')
await execWithSudo('apt-get --yes install libgtk-3-dev', {
stdout: process.stdout,
})
spinner.start('Attempting to install dependencies...')
if (missingDependencies.length !== 0) {
await installPackages(missingDependencies)
}
spinner.succeed()

// 3. Download the Moddable repository, or use the git command line tool as follows:
Expand Down Expand Up @@ -112,6 +114,7 @@ export default async function ({
process.env.MODDABLE = INSTALL_PATH
process.env.PATH = `${String(process.env.PATH)}:${BIN_PATH}`

await upsert(EXPORTS_FILE_PATH, `# Generated by xs-dev CLI`)
await upsert(EXPORTS_FILE_PATH, `export MODDABLE=${process.env.MODDABLE}`)
await upsert(EXPORTS_FILE_PATH, `export PATH="${BIN_PATH}:$PATH"`)

Expand Down
7 changes: 7 additions & 0 deletions src/toolbox/system/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export async function execWithSudo(
await system.exec(`sudo --askpass --preserve-env ${command}`, options)
}

export async function pkexec(
command: string,
options: Record<string, unknown> = {},
): Promise <void> {
await system.exec(`pkexec ${command}`, options)
}

/**
* Utility for updating in-memory process.env after running a command
*/
Expand Down
56 changes: 56 additions & 0 deletions src/toolbox/system/packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Dependency } from './types'
import { execWithSudo } from '../system/exec'
import { print, system } from 'gluegun'

/**
* Check if the list of dependencies are installed on the system.
**/
export async function findMissingDependencies(dependencies: Dependency[]): Promise<Dependency[]> {
const missingDependencies: Dependency[] = []

for (const dep of dependencies) {
if (dep.type === 'binary') {
if (system.which(dep.name) === null) {
missingDependencies.push(dep)
}
}
if (dep.type === 'library') {
try {
await system.run(`pkg-config --exists ${dep.name}`)
} catch (error) {
missingDependencies.push(dep)
}
}
if (dep.type === 'pylib') {
try {
await system.run(`pip3 show ${dep.name}`)
} catch (error) {
missingDependencies.push(dep)
}
}
}

return missingDependencies
}

/**
* Attempt to install packages on the linux platform.
**/
export async function installPackages(packages: Dependency[]): Promise<void> {
const packageManager = system.which('apt')

if (packageManager !== null && packageManager !== undefined) {
await execWithSudo(
`${packageManager} install --yes ${packages.map((p) => p.packageName).join(' ')}`,
{ stdout: process.stdout },
)
} else {
print.warning(
'xs-dev attempted to install dependencies, but your Linux distribution is not yet supported',
)
print.warning(
`Please install these dependencies before running this command again: ${packages.map((p) => p.packageName).join(', ')}`,
)
process.exit(1)
}
Comment on lines +39 to +55
Copy link
Owner

Choose a reason for hiding this comment

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

This is a handy abstraction. I wonder if it's worth exploring integration something like UPT on top of this as a follow up. 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll have to read up on it, but I like the idea of letting another tool do all the work for us.

}
5 changes: 5 additions & 0 deletions src/toolbox/system/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Dependency {
name: string
packageName: string
type: 'binary' | 'library' | 'pylib'
}
File renamed without changes.