|
| 1 | +#!/usr/bin/env node |
| 2 | + |
| 3 | +/* This Source Code Form is subject to the terms of the Mozilla Public |
| 4 | + * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 5 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| 6 | + |
| 7 | +//require('ts-node').register(); |
| 8 | +//require('./glean.ts'); |
| 9 | + |
| 10 | +import * as exec from "child_process"; |
| 11 | +import * as fs from "fs"; |
| 12 | +import * as path from "path"; |
| 13 | +import { argv, platform } from "process"; |
| 14 | +import { promisify } from "util"; |
| 15 | + |
| 16 | +// Define an async/await version of "exec". |
| 17 | +const execAsync = promisify(exec.exec); |
| 18 | + |
| 19 | +// The name of the directory which will contain the Python virtual environment |
| 20 | +// used to run the glean-parser. |
| 21 | +const VIRTUAL_ENVIRONMENT_DIR = ".venv"; |
| 22 | + |
| 23 | +// The version of glean_parser to install from PyPI. |
| 24 | +const GLEAN_PARSER_VERSION = "2.5.0"; |
| 25 | + |
| 26 | +// This script runs a given Python module as a "main" module, like |
| 27 | +// `python -m module`. However, it first checks that the installed |
| 28 | +// package is at the desired version, and if not, upgrades it using `pip`. |
| 29 | +// |
| 30 | +// ** IMPORTANT** |
| 31 | +// Keep this script in sync with the one in the Glean SDK (Gradle Plugin). |
| 32 | +// |
| 33 | +// Note: Groovy doesn't support embedded " in multi-line strings, so care |
| 34 | +// should be taken to use ' everywhere in this code snippet. |
| 35 | +const PYTHON_SCRIPT = ` |
| 36 | +import importlib |
| 37 | +import subprocess |
| 38 | +import sys |
| 39 | +offline = sys.argv[1] == 'offline' |
| 40 | +module_name = sys.argv[2] |
| 41 | +expected_version = sys.argv[3] |
| 42 | +try: |
| 43 | + module = importlib.import_module(module_name) |
| 44 | +except ImportError: |
| 45 | + found_version = None |
| 46 | +else: |
| 47 | + found_version = getattr(module, '__version__') |
| 48 | +if found_version != expected_version: |
| 49 | + if not offline: |
| 50 | + subprocess.check_call([ |
| 51 | + sys.executable, |
| 52 | + '-m', |
| 53 | + 'pip', |
| 54 | + 'install', |
| 55 | + '--upgrade', |
| 56 | + f'{module_name}=={expected_version}' |
| 57 | + ]) |
| 58 | + else: |
| 59 | + print(f'Using Python environment at {sys.executable},') |
| 60 | + print(f'expected glean_parser version {expected_version}, found {found_version}.') |
| 61 | + sys.exit(1) |
| 62 | +try: |
| 63 | + subprocess.check_call([ |
| 64 | + sys.executable, |
| 65 | + '-m', |
| 66 | + module_name |
| 67 | + ] + sys.argv[4:]) |
| 68 | +except: |
| 69 | + # We don't need to show a traceback in this helper script. |
| 70 | + # Only the output of the subprocess is interesting. |
| 71 | + sys.exit(1) |
| 72 | +`; |
| 73 | + |
| 74 | +/** |
| 75 | + * Gets the name of the Python binary, based on the host OS. |
| 76 | + * |
| 77 | + * @returns the name of the Python executable. |
| 78 | + */ |
| 79 | +function getSystemPythonBinName(): string { |
| 80 | + return (platform === "win32") ? "python.exe" : "python3"; |
| 81 | +} |
| 82 | + |
| 83 | +/** |
| 84 | + * Gets the full path to the directory containing the python |
| 85 | + * binaries in the virtual environment. |
| 86 | + * |
| 87 | + * Note that this directory changes depending on the host OS. |
| 88 | + * |
| 89 | + * @param venvRoot the root path of the virtual environment. |
| 90 | + * |
| 91 | + * @returns the full path to the directory containing the python |
| 92 | + * binaries in the virtual environment. |
| 93 | + */ |
| 94 | +function getPythonVenvBinariesPath(venvRoot: string): string { |
| 95 | + if (platform === "win32") { |
| 96 | + return path.join(venvRoot, "Scripts"); |
| 97 | + } |
| 98 | + |
| 99 | + return path.join(venvRoot, "bin"); |
| 100 | +} |
| 101 | + |
| 102 | +/** |
| 103 | + * Checks if a Python virtual environment is available. |
| 104 | + * |
| 105 | + * @param venvPath the Python virtual environment directory. |
| 106 | + * |
| 107 | + * @returns `true` if the Python virtual environment exists and |
| 108 | + * is accessible, `false` otherwise. |
| 109 | + */ |
| 110 | +async function checkPythonVenvExists(venvPath: string): Promise<boolean> { |
| 111 | + console.log(`Checking for a Glean virtual environment at ${venvPath}`); |
| 112 | + |
| 113 | + const venvPython = |
| 114 | + path.join(getPythonVenvBinariesPath(venvPath), getSystemPythonBinName()); |
| 115 | + |
| 116 | + const access = promisify(fs.access); |
| 117 | + |
| 118 | + try { |
| 119 | + await access(venvPath, fs.constants.F_OK); |
| 120 | + await access(venvPython, fs.constants.F_OK); |
| 121 | + |
| 122 | + return true; |
| 123 | + } catch (e) { |
| 124 | + return false; |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | +/** |
| 129 | + * Uses the system's Python interpreter to create a Python3 virtual environment. |
| 130 | + * |
| 131 | + * @param venvPath the directory in which to create the virtual environment. |
| 132 | + * |
| 133 | + * @returns `true` if the environment was correctly created, `false` otherwise. |
| 134 | + */ |
| 135 | +async function createPythonVenv(venvPath: string): Promise<boolean> { |
| 136 | + console.log(`Creating a Glean virtual environment at ${venvPath}`); |
| 137 | + |
| 138 | + const pipFilename = (platform === "win32") ? "pip3.exe" : "pip3"; |
| 139 | + const venvPip = |
| 140 | + path.join(getPythonVenvBinariesPath(VIRTUAL_ENVIRONMENT_DIR), pipFilename); |
| 141 | + |
| 142 | + const pipCmd = `${venvPip} install wheel`; |
| 143 | + const venvCmd = `${getSystemPythonBinName()} -m venv ${VIRTUAL_ENVIRONMENT_DIR}`; |
| 144 | + |
| 145 | + for (const cmd of [venvCmd, pipCmd]) { |
| 146 | + try { |
| 147 | + await execAsync(cmd); |
| 148 | + } catch (e) { |
| 149 | + console.error(e); |
| 150 | + return false; |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + return true; |
| 155 | +} |
| 156 | + |
| 157 | +/** |
| 158 | + * Checks if a virtual environment for running the glean_parser exists, |
| 159 | + * otherwise it creates it. |
| 160 | + * |
| 161 | + * @param projectRoot the project's root directory. |
| 162 | + */ |
| 163 | +async function setup(projectRoot: string) { |
| 164 | + const venvRoot = path.join(projectRoot, VIRTUAL_ENVIRONMENT_DIR); |
| 165 | + |
| 166 | + const venvExists = await checkPythonVenvExists(venvRoot); |
| 167 | + if (venvExists) { |
| 168 | + console.log(`Using Glean virtual environment at ${venvRoot}`); |
| 169 | + } else if (!await createPythonVenv(venvRoot)){ |
| 170 | + console.error(`Failed to createa a Glean virtual environment at ${venvRoot}`); |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +/** |
| 175 | + * Runs the glean_parser with the provided options. |
| 176 | + * |
| 177 | + * @param projectRoot the project's root directory. |
| 178 | + * @param parserArgs the list of arguments passed to this command. |
| 179 | + */ |
| 180 | +async function runGlean(projectRoot: string, parserArgs: string[]) { |
| 181 | + const venvRoot = path.join(projectRoot, VIRTUAL_ENVIRONMENT_DIR); |
| 182 | + const pythonBin = path.join(getPythonVenvBinariesPath(venvRoot), getSystemPythonBinName()); |
| 183 | + const cmd = `${pythonBin} -c "${PYTHON_SCRIPT}" online glean_parser ${GLEAN_PARSER_VERSION} ${parserArgs.join(" ")}`; |
| 184 | + try { |
| 185 | + await execAsync(cmd); |
| 186 | + } catch (e) { |
| 187 | + console.error(e); |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +/** |
| 192 | + * Runs the command. |
| 193 | + * |
| 194 | + * @param args the arguments passed to this process. |
| 195 | + */ |
| 196 | +async function run(args: string[]) { |
| 197 | + if (args.length < 3) { |
| 198 | + throw new Error("Not enough arguments. Please refer to https://mozilla.github.io/glean_parser/readme.html"); |
| 199 | + } |
| 200 | + |
| 201 | + const projectRoot = process.cwd(); |
| 202 | + try { |
| 203 | + await setup(projectRoot); |
| 204 | + } catch (err) { |
| 205 | + console.error("Failed to setup the Glean build environment", err); |
| 206 | + } |
| 207 | + |
| 208 | + await runGlean(projectRoot, args.slice(2)); |
| 209 | +} |
| 210 | + |
| 211 | +run(argv).catch(e => { |
| 212 | + console.error("There was an error running Glean", e); |
| 213 | +}); |
0 commit comments