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 Python setup process #2768

Merged
merged 1 commit into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion src/common/ui/interrupt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ interface ActionBase<T extends string> {
interface OpenUrlAction extends ActionBase<'open-url'> {
url: string;
}
interface RunAction extends ActionBase<'run'> {
action: () => void;
}

export type Action = OpenUrlAction;
export type Action = OpenUrlAction | RunAction;

export interface InteractionOption {
title: string;
Expand Down
215 changes: 145 additions & 70 deletions src/main/backend/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { CriticalError } from '../../common/ui/error';
import { ProgressToken } from '../../common/ui/progress';
import { getIntegratedFfmpeg, hasSystemFfmpeg } from '../ffmpeg/ffmpeg';
import { checkPythonPaths } from '../python/checkPythonPaths';
import { getIntegratedPython } from '../python/integratedPython';
import { getIntegratedPython, getIntegratedPythonExecutable } from '../python/integratedPython';
import { BackendProcess, BorrowedBackendProcess, OwnedBackendProcess } from './process';

const getValidPort = async () => {
Expand All @@ -27,60 +27,48 @@ const getValidPort = async () => {
return port;
};

const getPythonInfo = async (
token: ProgressToken,
useSystemPython: boolean,
systemPythonLocation: string | undefined | null,
rootDir: string
) => {
log.info('Attempting to check Python env...');
interface PythonSetupConfig {
integratedPythonFolder: string;
systemPythonLocation: string | undefined | null;
}
const getSystemPythonInfo = async (token: ProgressToken, config: PythonSetupConfig) => {
log.info('Attempting to check system Python env...');

let pythonInfo: PythonInfo;

let integratedPythonFolderPath = path.join(rootDir, '/python');
const { integratedPythonFolder } = config;
let { systemPythonLocation } = config;

if (systemPythonLocation) {
// eslint-disable-next-line no-param-reassign
systemPythonLocation = path.normalize(String(JSON.parse(systemPythonLocation)));
}
try {
systemPythonLocation = path.normalize(systemPythonLocation);
} catch (error) {
log.error(`Ignoring system python location because of error: ${String(error)}`);

if (integratedPythonFolderPath) {
integratedPythonFolderPath = path.normalize(integratedPythonFolderPath);
systemPythonLocation = null;
}
}

if (useSystemPython) {
try {
pythonInfo = await checkPythonPaths([
...(systemPythonLocation ? [systemPythonLocation] : []),
'python3',
'python',
// Fall back to integrated python if all else fails
integratedPythonFolderPath,
]);
if (pythonInfo.python === integratedPythonFolderPath) {
log.info('System python not found. Using integrated Python');
await token.submitInterrupt({
type: 'warning',
title: 'Python not installed or invalid version',
message:
'It seems like you do not have a valid version of Python installed on your system, or something went wrong with your installed instance.' +
' Please install Python (3.8+) if you would like to use system Python. You can get Python from https://www.python.org/downloads/.' +
' Be sure to select the add to PATH option. ChaiNNer will use its integrated Python for now.',
options: [
{
title: 'Get Python',
action: { type: 'open-url', url: 'https://www.python.org/downloads/' },
},
],
});
}
} catch (error) {
log.error(error);
throw new CriticalError({
title: 'Error checking for valid Python instance',
const integratedPython = getIntegratedPythonExecutable(integratedPythonFolder);

try {
const pythonInfo = await checkPythonPaths([
...(systemPythonLocation ? [systemPythonLocation] : []),
'python3',
'python',
// Fall back to integrated python if all else fails
integratedPython,
]);

if (pythonInfo.python === integratedPython) {
log.info('System python not found. Using integrated Python');
await token.submitInterrupt({
type: 'warning',
title: 'System Python not installed or invalid version',
message:
'It seems like you do not have a valid version of Python installed on your system, or something went wrong with your installed instance.' +
' Please install Python (3.8+) to use this application. You can get Python from https://www.python.org/downloads/. Be sure to select the add to PATH option.',
' Please install Python (3.8+) if you would like to use system Python. You can get Python from https://www.python.org/downloads/.' +
' Be sure to select the add to PATH option.' +
'\n\nChaiNNer will start using its integrated Python for now.' +
' You can change this later in the settings.',
options: [
{
title: 'Get Python',
Expand All @@ -89,33 +77,120 @@ const getPythonInfo = async (
],
});
}
} else {
// User is using integrated python
try {
pythonInfo = await getIntegratedPython(
integratedPythonFolderPath,
(percentage, stage) => {
token.submitProgress({
status:
stage === 'download'
? t('setup.downloadingPython', 'Downloading Integrated Python...')
: t('setup.extractingPython', 'Extracting downloaded files...'),
totalProgress: stage === 'download' ? 0.3 : 0.4,
statusProgress: percentage / 100,
});
}
);
} catch (error) {
log.error(error);

throw new CriticalError({
title: 'Unable to install integrated Python',
message:
`Chainner was unable to install its integrated Python environment.` +
` Please ensure that your computer is connected to the internet and that chainner has access to the network.`,
return pythonInfo;
} catch (error) {
log.error(error);
throw new CriticalError({
title: 'Error checking for valid Python instance',
message:
'It seems like you do not have a valid version of Python installed on your system, or something went wrong with your installed instance.' +
' Please install Python (3.8+) to use this application. You can get Python from https://www.python.org/downloads/. Be sure to select the add to PATH option.',
options: [
{
title: 'Get Python',
action: { type: 'open-url', url: 'https://www.python.org/downloads/' },
},
],
});
}
};
const getIntegratedPythonInfo = async (
token: ProgressToken,
config: PythonSetupConfig
): Promise<PythonInfo> => {
log.info('Attempting to check integrated Python env...');

const { integratedPythonFolder } = config;

// User is using integrated python
try {
return await getIntegratedPython(integratedPythonFolder, (percentage, stage) => {
token.submitProgress({
status:
stage === 'download'
? t('setup.downloadingPython', 'Downloading Integrated Python...')
: t('setup.extractingPython', 'Extracting downloaded files...'),
totalProgress: stage === 'download' ? 0.3 : 0.4,
statusProgress: percentage / 100,
});
});
} catch (error) {
log.error(error);

enum Action {
Retry,
System,
Crash,
}
let action: Action = Action.Crash as Action;
await token.submitInterrupt({
type: 'warning',
title: 'Unable to install integrated Python',
message:
'ChaiNNer needs and active internet connection and access to the network to install its integrated Python environment. Please ensure an active internet connection and try again.' +
'\n\nAlternatively, chaiNNer can use your system Python environment instead if you have Python 3.8+ installed. (Note that chaiNNer will install packages into your system Python environment if you choose this option.)' +
'\n\nChaiNNer requires a valid Python environment to run. Please choose one of the following options:',
options: [
{
title: 'Retry to install integrated Python',
action: {
type: 'run',
action: () => {
action = Action.Retry;
},
},
},
{
title: 'Use System Python instead',
action: {
type: 'run',
action: () => {
action = Action.System;
},
},
},
{
title: 'Exit',
action: {
type: 'run',
action: () => {
action = Action.Crash;
},
},
},
],
});

if (action === Action.Retry) {
return getIntegratedPythonInfo(token, config);
}
if (action === Action.System) {
return getSystemPythonInfo(token, config);
}

throw new CriticalError({
title: 'Unable to install integrated Python',
message:
`Chainner was unable to install its integrated Python environment.` +
` Please ensure that your computer is connected to the internet and that chainner has access to the network.`,
});
}
};
const getPythonInfo = async (
token: ProgressToken,
useSystemPython: boolean,
systemPythonLocation: string | undefined | null,
rootDir: string
) => {
const config: PythonSetupConfig = {
integratedPythonFolder: path.normalize(path.join(rootDir, '/python')),
systemPythonLocation,
};

const pythonInfo = useSystemPython
? await getSystemPythonInfo(token, config)
: await getIntegratedPythonInfo(token, config);

log.info(`Final Python binary: ${pythonInfo.python}`);
log.info(pythonInfo);
Expand Down
6 changes: 5 additions & 1 deletion src/main/cli/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ const addProgressListeners = (monitor: ProgressMonitor) => {
logger(` Go to ${action.url}`);
break;

case 'run':
// log nothing
break;

default:
return assertNever(action.type);
return assertNever(action);
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/main/gui/main-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ const setupProgressListeners = (
title: interrupt.title ?? 'Critical error occurred',
buttons: [...options.map((o) => o.title), 'Exit'],
defaultId: options.length,
cancelId: options.length,
message: interrupt.message,
};
} else {
Expand All @@ -431,6 +432,7 @@ const setupProgressListeners = (
title: interrupt.title ?? 'Critical error occurred',
buttons: [...options.map((o) => o.title), 'Ok'],
defaultId: options.length,
cancelId: options.length,
message: interrupt.message,
};
}
Expand All @@ -445,8 +447,12 @@ const setupProgressListeners = (
await shell.openExternal(action.url);
break;
}
case 'run': {
action.action();
break;
}
default:
return assertNever(action.type);
return assertNever(action);
}
} catch (error) {
log.error(`Failed to execute action of type ${action.type}`, error);
Expand Down
10 changes: 8 additions & 2 deletions src/main/python/integratedPython.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ const extractPython = async (
);
};

export const getIntegratedPythonExecutable = (directory: string): string => {
const platform = getPlatform();
const { path: relativePath } = downloads[platform];
return path.resolve(path.join(directory, relativePath));
};

/**
* Retrieves the path of the python executable of the integrated python installation.
*
Expand All @@ -67,9 +73,9 @@ export const getIntegratedPython = async (
onProgress: (percentage: number, stage: 'download' | 'extract') => void
): Promise<PythonInfo> => {
const platform = getPlatform();
const { url, version, path: relativePath } = downloads[platform];
const { url, version } = downloads[platform];

const pythonPath = path.resolve(path.join(directory, relativePath));
const pythonPath = getIntegratedPythonExecutable(directory);
const pythonBinExists = await checkFileExists(pythonPath);

if (pythonBinExists) {
Expand Down
Loading