Skip to content

Commit

Permalink
Merge pull request #59 from jsnbuchanan/fix/yarn-install-feedback-for…
Browse files Browse the repository at this point in the history
…-missing-python3.10-dependency

Give mac developers feedback when `yarn` install fails for missing Python 3.10 dependency
  • Loading branch information
betterbrand authored Mar 29, 2024
2 parents 963edf8 + e8a570b commit 4ae6e2e
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .setup/error-with-remedy.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export class ErrorWithRemedy extends Error {
constructor(errorMessage, remedyMessage) {
super(errorMessage);
this.name = this.constructor.name;
this.remedy = remedyMessage;

// Maintaining proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
23 changes: 23 additions & 0 deletions .setup/format.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function formatRed(message) {
return `\x1b[31m${message}\x1b[0m`;
}

export function formatGray(message) {
return `\x1b[37m${message}\x1b[0m`;
}

export function formatItalics(message) {
return `\x1b[3m${message}\x1b[0m`;
}

export function formatBold(message) {
return `\x1b[1m${message}\x1b[0m`;
}

export function formatError(message) {
return formatBold(formatRed(message));
}

export function formatExample(message) {
return formatGray(formatItalics(message));
}
21 changes: 21 additions & 0 deletions .setup/log.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { formatBold, formatError, formatGray } from './format.mjs';

export function logInfo(message) {
console.log(formatGray(message));
}

/**
* Log an error to the console with an error `message` and optional `remedy`
* @param error in the format { message: string, remedy?: string }
* @returns void
*/
export function logError(error) {
console.error();
console.error(formatError(error?.message || error));
if (error?.remedy) {
console.error();
console.error(formatBold('Suggested remedy:'));
console.error(error.remedy);
}
console.error();
}
13 changes: 13 additions & 0 deletions .setup/macos/python-path.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { logInfo } from '../log.mjs';
import { execSync } from 'child_process';

export function getPythonPath() {
const nodeGypPythonPath = process.env.NODE_GYP_FORCE_PYTHON;
if (nodeGypPythonPath) {
logInfo(`NODE_GYP_FORCE_PYTHON=${nodeGypPythonPath}`);
return nodeGypPythonPath;
}
logInfo(`defaulting to system's python3`);
return execSync(`which python3`).toString().trim();
}

27 changes: 27 additions & 0 deletions .setup/macos/validate-executable.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import fs from 'fs';

export function validateExecutable(path) {
existsOnSystem(path)
isNotADirectory(path)
isExecutable(path)
}

function existsOnSystem(path) {
if (!fs.existsSync(path)) {
throw new Error(`Path ${path} does not exist`);
}
}

function isNotADirectory(path) {
if (fs.statSync(path).isDirectory()) {
throw new Error(`${path} is a directory. Please provide the path to an executable.`);
}
}

function isExecutable(path) {
try {
fs.accessSync(path, fs.constants.X_OK);
} catch (err) {
throw new Error(`${path} is not executable`);
}
}
53 changes: 53 additions & 0 deletions .setup/macos/validate-setup.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { execSync } from 'child_process';
import { ErrorWithRemedy } from '../error-with-remedy.mjs';
import { formatExample } from '../format.mjs';
import { getPythonPath } from './python-path.mjs';
import { logInfo } from '../log.mjs';
import { validateExecutable } from './validate-executable.mjs';

/**
* On macOS, this script checks if Python 3.10 is installed and accessible to node-gyp.
*
* I ran into a problem trying to `yarn` install, with a system Python version of `3.12.2`,
* but ran into the error `ModuleNotFoundError: No module named 'distutils'`.
* Since node-gyp relies on `distutils`, which is removed in Python `3.12`,
* you need to use a Python version that still includes `distutils`.
*/
export function validateMacSetup() {
logInfo('Installing on macOS');
const pythonPath = getPythonPath();
validateExecutable(pythonPath);

let error;
try {
const pythonVersionOutput = execSync(`${pythonPath} --version`).toString().trim();
logInfo(`${pythonPath} == (${pythonVersionOutput})`);

const pythonVersion = pythonVersionOutput.split(' ')[1].trim();
const majorVersion = parseInt(pythonVersion.split('.')[0]);
const minorVersion = parseInt(pythonVersion.split('.')[1]);
const noCompatiblePythonVersionFound = !(majorVersion === 3 && (minorVersion >= 10 && minorVersion < 12));

if (noCompatiblePythonVersionFound) {
error = `Incompatible Python version ${pythonVersion} found. Python 3.10 is required.`;
}

} catch (caughtError) {
error = `Python 3.10 was not found with error: ${caughtError?.message || caughtError}`;
}
if (error) {
const checkForPythonInstall = 'Check for versions of python installed on your system. For example, if you use brew:';
const displayBrewPythonVersionsExample = formatExample('brew list --versions | grep python');

const pleaseInstallPython = 'If python 3.10 was not found, install it. For example:';
const installPythonExample = formatExample('brew install [email protected]');

const configureNodeGypPython = 'Ensure you have an environment variable for NODE_GYP_FORCE_PYTHON pointing to your python 3.10 path.\n For example, assuming you installed [email protected] with brew:';
const exportNodeGypPythonEnvVariable = formatExample('export NODE_GYP_FORCE_PYTHON=$(brew --prefix [email protected])/bin/python3.10');

throw new ErrorWithRemedy(error, ` STEP 1: ${checkForPythonInstall} ${displayBrewPythonVersionsExample}
\n STEP 2: ${pleaseInstallPython} ${installPythonExample}
\n STEP 3: ${configureNodeGypPython} ${exportNodeGypPythonEnvVariable}`
);
}
}
20 changes: 20 additions & 0 deletions .setup/yarn-preinstall-system-validation.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as os from 'os';
import { validateMacSetup } from './macos/validate-setup.mjs';
import { logError } from './log.mjs';

/**
* Validate the system setup at the beginning of `yarn` install.
*/
try {
const platform = os.platform();
switch (platform) {
case 'darwin':
validateMacSetup();
break;
default:
console.log(`No setup validation required for platform ${platform}`);
}
} catch(setupError) {
logError(setupError);
process.exit(1);
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"description": "Morpheus is private, sovereign, AI",
"main": ".webpack/main",
"scripts": {
"preinstall": "node .setup/yarn-preinstall-system-validation.mjs",
"start": "cross-env NODE_ENV=development DEBUG=electron-packager electron-forge start",
"package": "set DEBUG=electron-packager && electron-forge package",
"make": "electron-forge make",
Expand Down

0 comments on commit 4ae6e2e

Please sign in to comment.