Skip to content

Commit 6a7066b

Browse files
authored
Merge pull request #105 from Dexterp37/glean_cmd
Expose a "glean" command
2 parents ba54710 + 1c0b020 commit 6a7066b

File tree

10 files changed

+256
-37
lines changed

10 files changed

+256
-37
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,6 @@ web-ext-artifacts/
121121

122122
# This is the name of the folder we will add the glean generated files in for our samples.
123123
generated/
124+
125+
# This is the name of the Glean virtual environment
126+
.venv

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* [#101](https://github.com/mozilla/glean.js/pull/101): BUGFIX: Only validate Debug View Tag and Source Tags when they are present.
1212
* [#102](https://github.com/mozilla/glean.js/pull/102): BUGFIX: Include a Glean User-Agent header in all pings.
1313
* [#97](https://github.com/mozilla/glean.js/pull/97): Add support for labeled metric types (string, boolean and counter).
14+
* [#105](https://github.com/mozilla/glean.js/pull/105): Introduce and publish the `glean` command for using the `glean-parser` in a virtual environment.
1415

1516
# v0.4.0 (2021-03-10)
1617

glean/package-lock.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

glean/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
"package.json",
4747
"dist/**/*"
4848
],
49+
"bin": {
50+
"glean": "./dist/cli/cli.js"
51+
},
4952
"scripts": {
5053
"test": "npm run test:core && npm run test:platform && npm run test:plugins",
5154
"test:core": "ts-mocha \"tests/core/**/*.spec.ts\" --recursive",
@@ -54,13 +57,14 @@
5457
"build:test-webext": "cd tests/platform/utils/webext/sample/ && npm install && npm run build:xpi",
5558
"lint": "eslint . --ext .ts,.js,.json --max-warnings=0",
5659
"fix": "eslint . --ext .ts,.js,.json --fix",
60+
"build:cli": "tsc -p ./tsconfig/cli.json",
5761
"build:webext:lib:esm": "tsc -p ./tsconfig/webext/esm.json",
5862
"build:webext:lib:cjs": "tsc -p ./tsconfig/webext/cjs.json",
5963
"build:webext:lib:browser": "tsc -p ./tsconfig/webext/browser.json",
6064
"build:webext:types": "tsc -p ./tsconfig/webext/types.json",
6165
"build:webext": "rm -rf dist/webext && npm run build:webext:lib:esm && npm run build:webext:lib:cjs && npm run build:webext:lib:browser && npm run build:webext:types",
6266
"build:qt": "webpack --config webpack.config.qt.js --mode production",
63-
"prepublishOnly": "cp ../README.md ./README.md && npm run build:webext",
67+
"prepublishOnly": "cp ../README.md ./README.md && npm run build:cli && npm run build:webext",
6468
"postpublish": "rm ./README.md"
6569
},
6670
"repository": {

glean/src/cli.ts

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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+
});

glean/tsconfig/cli.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "./base.json",
3+
"include": [
4+
"../src/cli.ts"
5+
],
6+
"compilerOptions": {
7+
"outDir": "../dist/cli"
8+
}
9+
}

samples/web-extension/javascript/README.md

+8-15
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,30 @@ Whenever this web extensions popup is opened it will trigger Glean.js events.
99
> of the directory where the manifest file is.
1010
1111
## How to run this sample
12+
Running the example requires Python 3.
1213

13-
1. Generate metrics and pings files.
14-
15-
```bash
16-
npm run glean_parser
17-
```
18-
19-
> This command requires that you have [`glean_parser`](https://pypi.org/project/glean-parser/) available.
20-
> glean_parser is a Python package. To install it run `pip install glean_parser`.
21-
> Javascript support was added to glean_parser on version 2.1.0, make sure your version is up to date.
22-
23-
2. Link the `@mozilla/glean` package. On the glean/ folder run:
14+
1. Link the `@mozilla/glean` package. On the glean/ folder run:
2415

2516
```bash
2617
npm link
2718
```
2819

29-
3. Link the `@mozilla/glean` package to this sample web extension. On this `web-extension` folder run:
20+
2. Link the `@mozilla/glean` package to this sample web extension. On this `web-extension` folder run:
3021

3122
```bash
23+
npm install
3224
npm link @mozilla/glean
3325
```
3426

35-
4. Build this sample. On this `web-extension` folder run:
27+
3. Build this sample. On this `web-extension` folder run:
3628

3729
```bash
38-
npm install
3930
npm run build
4031
```
4132

42-
5. Load the web extension on your browser of choice.
33+
> **Note** This operation will take some time on the first run, because it will create a virtual environment for running the glean-parser.
34+
35+
4. Load the web extension on your browser of choice.
4336

4437
- **Firefox**
4538
1. Go to [about:debugging#/runtime/this-firefox](about:debugging#/runtime/this-firefox);

samples/web-extension/javascript/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"description": "A sample web extension using Glean.js",
55
"main": "dist/bundle.js",
66
"scripts": {
7-
"glean_parser": "glean_parser translate src/metrics.yaml src/pings.yaml -f javascript -o src/generated",
8-
"build": "webpack --watch --config webpack.config.js --mode production",
9-
"dev": "webpack --watch --config webpack.config.js --mode development"
7+
"glean": "glean translate src/metrics.yaml src/pings.yaml -f javascript -o src/generated",
8+
"build": "npm run glean && webpack --watch --config webpack.config.js --mode production",
9+
"dev": "npm run glean && webpack --watch --config webpack.config.js --mode development"
1010
},
1111
"author": "The Glean Team <[email protected]>",
1212
"license": "MPL-2.0",

samples/web-extension/typescript/README.md

+8-15
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,30 @@ Whenever this web extensions popup is opened it will trigger Glean.js events.
99
> of the directory where the manifest file is.
1010
1111
## How to run this sample
12+
Running the example requires Python 3.
1213

13-
1. Generate metrics and pings files.
14-
15-
```bash
16-
npm run glean_parser
17-
```
18-
19-
> This command requires that you have [`glean_parser`](https://pypi.org/project/glean-parser/) available.
20-
> glean_parser is a Python package. To install it run `pip install glean_parser`.
21-
> Javascript support was added to glean_parser on version 2.1.0, make sure your version is up to date.
22-
23-
2. Link the `@mozilla/glean` package. On the glean/ folder run:
14+
1. Link the `@mozilla/glean` package. On the glean/ folder run:
2415

2516
```bash
2617
npm link
2718
```
2819

29-
3. Link the `@mozilla/glean` package to this sample web extension. On this `web-extension` folder run:
20+
2. Link the `@mozilla/glean` package to this sample web extension. On this `web-extension` folder run:
3021

3122
```bash
23+
npm install
3224
npm link @mozilla/glean
3325
```
3426

35-
4. Build this sample. On this `web-extension` folder run:
27+
3. Build this sample. On this `web-extension` folder run:
3628

3729
```bash
38-
npm install
3930
npm run build
4031
```
4132

42-
5. Load the web extension on your browser of choice.
33+
> **Note** This operation will take some time on the first run, because it will create a virtual environment for running the glean-parser.
34+
35+
4. Load the web extension on your browser of choice.
4336

4437
- **Firefox**
4538
1. Go to [about:debugging#/runtime/this-firefox](about:debugging#/runtime/this-firefox);

samples/web-extension/typescript/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"description": "A sample web extension using Glean.js",
55
"main": "dist/bundle.js",
66
"scripts": {
7-
"glean_parser": "glean_parser translate src/metrics.yaml src/pings.yaml -f typescript -o src/generated",
8-
"build": "webpack --watch --config webpack.config.js --mode production",
9-
"dev": "webpack --watch --config webpack.config.js --mode development"
7+
"glean": "glean translate src/metrics.yaml src/pings.yaml -f typescript -o src/generated",
8+
"build": "npm run glean && webpack --watch --config webpack.config.js --mode production",
9+
"dev": "npm run glean && webpack --watch --config webpack.config.js --mode development"
1010
},
1111
"author": "The Glean Team <[email protected]>",
1212
"license": "MPL-2.0",

0 commit comments

Comments
 (0)