Download pip and avoid venv in embedded Python

This commit is contained in:
Tiago Koji Castro Shibata 2018-08-03 21:44:12 -07:00
Родитель 6306ce5200
Коммит 2bfef9e406
2 изменённых файлов: 104 добавлений и 56 удалений

Просмотреть файл

@ -7,8 +7,8 @@ import * as yauzl from 'yauzl';
import { mkdir, winmlDataFoler } from '../persistence/appData';
const venv = path.join(winmlDataFoler, 'venv');
export const localPython = path.join(winmlDataFoler, 'python/python');
const venv = mkdir(winmlDataFoler, 'venv');
export const localPython = path.join(venv, 'Scripts/python');
function filterPythonBinaries(binaries: string[]) {
// Look for binaries with venv support
@ -24,64 +24,106 @@ function filterPythonBinaries(binaries: string[]) {
}
export function getPythonBinaries() {
const binaries = filterPythonBinaries([localPython, 'python3', 'py', 'python']) as Array<string | null>;
if (process.platform === 'win32' && binaries[0] !== localPython) {
const binaries = filterPythonBinaries(['python3', 'py', 'python']) as Array<string | null>;
if (process.platform === 'win32') {
binaries.push(null); // use null to represent local, embedded version
}
return [...new Set(binaries)];
}
function unzip(buffer: Buffer, directory: string, success: () => void, errorCallback: (err: Error) => void) {
yauzl.fromBuffer(buffer, (err, zipfile) => {
if (err) {
errorCallback(err);
return;
}
zipfile!.once('end', success);
zipfile!.once('error', errorCallback);
zipfile!.on('entry', entry => {
zipfile!.openReadStream(entry, (error, readStream) => {
if (error) {
errorCallback(error);
return;
}
readStream!.once('error', errorCallback);
readStream!.pipe(fs.createWriteStream(path.join(directory, entry.fileName)));
async function unzip(buffer: Buffer, directory: string) {
return new Promise((resolve, reject) => {
yauzl.fromBuffer(buffer, (err, zipfile) => {
if (err) {
return reject(err);
}
zipfile!.once('end', resolve);
zipfile!.once('error', reject);
zipfile!.on('entry', entry => {
zipfile!.openReadStream(entry, (error, readStream) => {
if (error) {
return reject(error);
}
readStream!.once('error', reject);
readStream!.pipe(fs.createWriteStream(path.join(directory, entry.fileName)));
});
});
});
});
}
export function downloadPython(success: () => void, error: (err: Error) => void) {
if (process.platform !== 'win32') {
throw new Error('Unsupported platform');
}
const pythonUrl = 'https://www.python.org/ftp/python/3.7.0/python-3.7.0-embed-amd64.zip';
const pythonFolder = mkdir(winmlDataFoler, 'python');
const data: Buffer[] = [];
https.get(pythonUrl, response => {
response
.on('data', (x: string) => data.push(Buffer.from(x, 'binary')))
.on('end', () => unzip(Buffer.concat(data), pythonFolder, success, error))
.on('error', error);
// Change the PTH to discover modules from Lib/site-packages, so that pip modules can be found
const pythonPth = `Lib/site-packages
python36.zip
.
# Uncomment to run site.main() automatically
import site`;
export async function downloadPython() {
return new Promise((resolve, reject) => {
if (process.platform !== 'win32') {
reject('Unsupported platform');
}
const pythonUrl = 'https://www.python.org/ftp/python/3.7.0/python-3.7.0-embed-amd64.zip';
const pythonFolder = mkdir(venv, 'Scripts');
const data: Buffer[] = [];
https.get(pythonUrl, response => {
response
.on('data', (x: string) => data.push(Buffer.from(x, 'binary')))
.on('end', async () => {
try {
await unzip(Buffer.concat(data), pythonFolder);
fs.writeFileSync(path.join(pythonFolder, 'python37._pth'), pythonPth);
resolve();
} catch (err) {
reject(err);
}
})
.on('error', reject);
});
});
}
export function getVenvPython() {
// Get Python installed in local venv
return filterPythonBinaries([path.join(venv, 'Scripts/python'), path.join(venv, 'bin/python')])[0];
for (const binary of [localPython, path.join(venv, 'bin/python')]) {
if (fs.existsSync(binary)) {
return binary;
}
}
return;
}
export async function installPip() {
// Python embedded distribution for Windows doesn't have pip
return new Promise((resolve, reject) => {
const data: Buffer[] = [];
https.get('https://bootstrap.pypa.io/get-pip.py', response => {
response
.on('data', (x: string) => data.push(Buffer.from(x, 'binary')))
.on('end', async () => {
try {
const getPip = path.join(venv, 'get-pip.py');
fs.writeFileSync(getPip, Buffer.concat(data));
await python(getPip);
resolve();
} catch (err) {
reject(err);
}
})
.on('error', reject);
});
});
}
export async function python(...command: string[]) {
return await promisify(execFile)(getVenvPython()!, ['-m', ...command]);
return await promisify(execFile)(getVenvPython()!, [...command]);
}
export async function pip(...command: string[]) {
return await python('pip', ...command);
return await python('-m', 'pip', ...command);
}
export async function installVenv(targetPython: string) {
await promisify(execFile)(targetPython, ['-m', 'venv', venv]);
await pip('install', '-U', 'pip');
await pip('install', '-r', path.join(__filename, 'requirements.txt'));
}

Просмотреть файл

@ -2,17 +2,19 @@ import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { Spinner } from 'office-ui-fabric-react/lib/Spinner';
import * as path from 'path';
import * as React from 'react';
import { winmlDataFoler } from '../../persistence/appData';
import { downloadPython, getPythonBinaries, getVenvPython, installVenv, localPython } from '../../python/python';
import { downloadPython, getPythonBinaries, getVenvPython, installVenv, pip } from '../../python/python';
import './View.css';
enum InstallationStep {
NotInstalling,
Downloading,
Installing,
CreatingVenv,
InstallingRequirements,
}
interface IComponentState {
@ -21,7 +23,7 @@ interface IComponentState {
}
export default class ConvertView extends React.Component<{}, IComponentState> {
private venvPython: string;
private venvPython: string | undefined;
constructor(props: {}) {
super(props);
@ -47,8 +49,10 @@ export default class ConvertView extends React.Component<{}, IComponentState> {
switch (this.state.installationStep) {
case InstallationStep.Downloading:
return <Spinner label="Downloading Python..." />;
case InstallationStep.Installing:
return <Spinner label="Downloading and installing converter..." />;
case InstallationStep.CreatingVenv:
return <Spinner label="Creating virtual environment..." />;
case InstallationStep.InstallingRequirements:
return <Spinner label="Downloading and installing requirements..." />;
}
this.venvPython = this.venvPython || getVenvPython();
if (!this.venvPython) {
@ -60,12 +64,19 @@ export default class ConvertView extends React.Component<{}, IComponentState> {
private pythonChooser = () => {
const binaries = getPythonBinaries();
const options = binaries.map((key) => key ? { key, text: key } : { key: '__download', text: 'Download a new Python binary to be used exclusively by the WinML Dashboard' });
const onChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption) => {
if (option.key === '__download') {
this.setState({ installationStep: InstallationStep.Downloading });
downloadPython(() => this.installVenv(localPython), (err) => this.setState({ error: err }));
} else {
this.installVenv(option.key);
const onChange = async (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption) => {
try {
if (option.key === '__download') {
this.setState({ installationStep: InstallationStep.Downloading });
await downloadPython();
} else {
this.setState({ installationStep: InstallationStep.CreatingVenv });
await installVenv(option.key);
}
await this.installRequirements();
this.setState({ installationStep: InstallationStep.NotInstalling });
} catch (error) {
this.setState({ error, installationStep: InstallationStep.NotInstalling });
}
}
return (
@ -77,14 +88,9 @@ export default class ConvertView extends React.Component<{}, IComponentState> {
);
}
private installVenv = async (targetPython: string) => {
this.setState({ installationStep: InstallationStep.Installing });
try {
await installVenv(targetPython);
this.setState({ installationStep: InstallationStep.NotInstalling });
} catch (error) {
this.setState({ error, installationStep: InstallationStep.NotInstalling });
}
private installRequirements = async () => {
this.setState({ installationStep: InstallationStep.InstallingRequirements });
await pip('install', '-r', path.join(__filename, 'requirements.txt'));
}
private convert = () => {