tidy: use a lockfile for js tools instead of npx
this makes us less vulnerable to MITM and supply chain attacks. it also means that the CI scripts are no longer responsible for tracking the versions of these tools. it should also avoid the situation where local tsc and CI disagree on the presense of errors due to them being different versions.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -85,8 +85,6 @@ __pycache__/
|
||||
|
||||
## Node
|
||||
node_modules
|
||||
package-lock.json
|
||||
package.json
|
||||
/src/doc/rustc-dev-guide/mermaid.min.js
|
||||
|
||||
## Rustdoc GUI tests
|
||||
|
||||
3232
package-lock.json
generated
Normal file
3232
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
package.json
Normal file
9
package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"browser-ui-test": "^0.21.1",
|
||||
"es-check": "^6.2.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-js": "github:eslint/js",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,6 @@ COPY scripts/nodejs.sh /scripts/
|
||||
RUN sh /scripts/nodejs.sh /node
|
||||
ENV PATH="/node/bin:${PATH}"
|
||||
|
||||
# Install es-check
|
||||
# Pin its version to prevent unrelated CI failures due to future es-check versions.
|
||||
RUN npm install es-check@6.1.1 eslint@8.6.0 typescript@5.7.3 -g
|
||||
|
||||
COPY scripts/sccache.sh /scripts/
|
||||
RUN sh /scripts/sccache.sh
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ pub fn check(
|
||||
ci_info: &CiInfo,
|
||||
librustdoc_path: &Path,
|
||||
tools_path: &Path,
|
||||
src_path: &Path,
|
||||
bless: bool,
|
||||
extra_checks: Option<&str>,
|
||||
pos_args: &[String],
|
||||
@@ -62,7 +61,6 @@ pub fn check(
|
||||
ci_info,
|
||||
librustdoc_path,
|
||||
tools_path,
|
||||
src_path,
|
||||
bless,
|
||||
extra_checks,
|
||||
pos_args,
|
||||
@@ -77,7 +75,6 @@ fn check_impl(
|
||||
ci_info: &CiInfo,
|
||||
librustdoc_path: &Path,
|
||||
tools_path: &Path,
|
||||
src_path: &Path,
|
||||
bless: bool,
|
||||
extra_checks: Option<&str>,
|
||||
pos_args: &[String],
|
||||
@@ -295,13 +292,17 @@ fn check_impl(
|
||||
spellcheck_runner(&args)?;
|
||||
}
|
||||
|
||||
if js_lint || js_typecheck {
|
||||
rustdoc_js::npm_install(root_path, outdir)?;
|
||||
}
|
||||
|
||||
if js_lint {
|
||||
rustdoc_js::lint(librustdoc_path, tools_path, src_path)?;
|
||||
rustdoc_js::es_check(librustdoc_path)?;
|
||||
rustdoc_js::lint(outdir, librustdoc_path, tools_path)?;
|
||||
rustdoc_js::es_check(outdir, librustdoc_path)?;
|
||||
}
|
||||
|
||||
if js_typecheck {
|
||||
rustdoc_js::typecheck(librustdoc_path)?;
|
||||
rustdoc_js::typecheck(outdir, librustdoc_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -3,12 +3,61 @@
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::process::{Child, Command};
|
||||
use std::{fs, io};
|
||||
|
||||
use build_helper::ci::CiEnv;
|
||||
use ignore::DirEntry;
|
||||
|
||||
use crate::walk::walk_no_read;
|
||||
|
||||
fn node_module_bin(outdir: &Path, name: &str) -> PathBuf {
|
||||
outdir.join("node_modules/.bin").join(name)
|
||||
}
|
||||
|
||||
fn spawn_cmd(cmd: &mut Command) -> Result<Child, io::Error> {
|
||||
cmd.spawn().map_err(|err| {
|
||||
eprintln!("unable to run {cmd:?} due to {err:?}");
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
/// install all js dependencies from package.json.
|
||||
pub(super) fn npm_install(root_path: &Path, outdir: &Path) -> Result<(), super::Error> {
|
||||
let copy_to_build = |p| {
|
||||
fs::copy(root_path.join(p), outdir.join(p)).map_err(|e| {
|
||||
eprintln!("unable to copy {p:?} to build directory: {e:?}");
|
||||
e
|
||||
})
|
||||
};
|
||||
// copy stuff to the output directory to make node_modules get put there.
|
||||
copy_to_build("package.json")?;
|
||||
copy_to_build("package-lock.json")?;
|
||||
|
||||
let mut cmd = Command::new("npm");
|
||||
if CiEnv::is_ci() {
|
||||
// `npm ci` redownloads every time and thus is too slow for local development.
|
||||
cmd.arg("ci");
|
||||
} else {
|
||||
cmd.arg("install");
|
||||
}
|
||||
// disable a bunch of things we don't want.
|
||||
// this makes tidy output less noisy, and also significantly improves runtime
|
||||
// of repeated tidy invokations.
|
||||
cmd.args(&["--audit=false", "--save=false", "--fund=false"]);
|
||||
cmd.current_dir(outdir);
|
||||
let mut child = spawn_cmd(&mut cmd)?;
|
||||
match child.wait() {
|
||||
Ok(exit_status) => {
|
||||
if exit_status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
Err(super::Error::FailedCheck("npm install"))
|
||||
}
|
||||
Err(error) => Err(super::Error::Generic(format!("npm install failed: {error:?}"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn rustdoc_js_files(librustdoc_path: &Path) -> Vec<PathBuf> {
|
||||
let mut files = Vec::new();
|
||||
walk_no_read(
|
||||
@@ -21,13 +70,13 @@ fn rustdoc_js_files(librustdoc_path: &Path) -> Vec<PathBuf> {
|
||||
return files;
|
||||
}
|
||||
|
||||
fn run_eslint(args: &[PathBuf], config_folder: PathBuf) -> Result<(), super::Error> {
|
||||
let mut child = Command::new("npx")
|
||||
.arg("eslint")
|
||||
fn run_eslint(outdir: &Path, args: &[PathBuf], config_folder: PathBuf) -> Result<(), super::Error> {
|
||||
let mut child = spawn_cmd(
|
||||
Command::new(node_module_bin(outdir, "eslint"))
|
||||
.arg("-c")
|
||||
.arg(config_folder.join(".eslintrc.js"))
|
||||
.args(args)
|
||||
.spawn()?;
|
||||
.args(args),
|
||||
)?;
|
||||
match child.wait() {
|
||||
Ok(exit_status) => {
|
||||
if exit_status.success() {
|
||||
@@ -39,77 +88,31 @@ fn run_eslint(args: &[PathBuf], config_folder: PathBuf) -> Result<(), super::Err
|
||||
}
|
||||
}
|
||||
|
||||
fn get_eslint_version_inner(global: bool) -> Option<String> {
|
||||
let mut command = Command::new("npm");
|
||||
command.arg("list").arg("--parseable").arg("--long").arg("--depth=0");
|
||||
if global {
|
||||
command.arg("--global");
|
||||
}
|
||||
let output = command.output().ok()?;
|
||||
let lines = String::from_utf8_lossy(&output.stdout);
|
||||
lines.lines().find_map(|l| l.split(':').nth(1)?.strip_prefix("eslint@")).map(|v| v.to_owned())
|
||||
}
|
||||
|
||||
fn get_eslint_version() -> Option<String> {
|
||||
get_eslint_version_inner(false).or_else(|| get_eslint_version_inner(true))
|
||||
}
|
||||
|
||||
pub(super) fn lint(
|
||||
outdir: &Path,
|
||||
librustdoc_path: &Path,
|
||||
tools_path: &Path,
|
||||
src_path: &Path,
|
||||
) -> Result<(), super::Error> {
|
||||
let eslint_version_path = src_path.join("ci/docker/host-x86_64/tidy/eslint.version");
|
||||
let eslint_version = match std::fs::read_to_string(&eslint_version_path) {
|
||||
Ok(version) => version.trim().to_string(),
|
||||
Err(error) => {
|
||||
eprintln!("failed to read `{}`: {error:?}", eslint_version_path.display());
|
||||
return Err(error.into());
|
||||
}
|
||||
};
|
||||
// Having the correct `eslint` version installed via `npm` isn't strictly necessary, since we're invoking it via `npx`,
|
||||
// but this check allows the vast majority that is not working on the rustdoc frontend to avoid the penalty of running
|
||||
// `eslint` in tidy. See also: https://github.com/rust-lang/rust/pull/142851
|
||||
match get_eslint_version() {
|
||||
Some(version) => {
|
||||
if version != eslint_version {
|
||||
// unfortunatly we can't use `Error::Version` here becuse `str::trim` isn't const and
|
||||
// Version::required must be a static str
|
||||
return Err(super::Error::Generic(format!(
|
||||
"⚠️ Installed version of eslint (`{version}`) is different than the \
|
||||
one used in the CI (`{eslint_version}`)\n\
|
||||
You can install this version using `npm update eslint` or by using \
|
||||
`npm install eslint@{eslint_version}`\n
|
||||
"
|
||||
)));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
//eprintln!("`eslint` doesn't seem to be installed. Skipping tidy check for JS files.");
|
||||
//eprintln!("You can install it using `npm install eslint@{eslint_version}`");
|
||||
return Err(super::Error::MissingReq(
|
||||
"eslint",
|
||||
"js lint checks",
|
||||
Some(format!("You can install it using `npm install eslint@{eslint_version}`")),
|
||||
));
|
||||
}
|
||||
}
|
||||
let files_to_check = rustdoc_js_files(librustdoc_path);
|
||||
println!("Running eslint on rustdoc JS files");
|
||||
run_eslint(&files_to_check, librustdoc_path.join("html/static"))?;
|
||||
run_eslint(outdir, &files_to_check, librustdoc_path.join("html/static"))?;
|
||||
|
||||
run_eslint(&[tools_path.join("rustdoc-js/tester.js")], tools_path.join("rustdoc-js"))?;
|
||||
run_eslint(&[tools_path.join("rustdoc-gui/tester.js")], tools_path.join("rustdoc-gui"))?;
|
||||
run_eslint(outdir, &[tools_path.join("rustdoc-js/tester.js")], tools_path.join("rustdoc-js"))?;
|
||||
run_eslint(
|
||||
outdir,
|
||||
&[tools_path.join("rustdoc-gui/tester.js")],
|
||||
tools_path.join("rustdoc-gui"),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn typecheck(librustdoc_path: &Path) -> Result<(), super::Error> {
|
||||
pub(super) fn typecheck(outdir: &Path, librustdoc_path: &Path) -> Result<(), super::Error> {
|
||||
// use npx to ensure correct version
|
||||
let mut child = Command::new("npx")
|
||||
.arg("tsc")
|
||||
let mut child = spawn_cmd(
|
||||
Command::new(node_module_bin(outdir, "tsc"))
|
||||
.arg("-p")
|
||||
.arg(librustdoc_path.join("html/static/js/tsconfig.json"))
|
||||
.spawn()?;
|
||||
.arg(librustdoc_path.join("html/static/js/tsconfig.json")),
|
||||
)?;
|
||||
match child.wait() {
|
||||
Ok(exit_status) => {
|
||||
if exit_status.success() {
|
||||
@@ -121,15 +124,14 @@ pub(super) fn typecheck(librustdoc_path: &Path) -> Result<(), super::Error> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn es_check(librustdoc_path: &Path) -> Result<(), super::Error> {
|
||||
pub(super) fn es_check(outdir: &Path, librustdoc_path: &Path) -> Result<(), super::Error> {
|
||||
let files_to_check = rustdoc_js_files(librustdoc_path);
|
||||
// use npx to ensure correct version
|
||||
let mut cmd = Command::new("npx");
|
||||
cmd.arg("es-check").arg("es2019");
|
||||
let mut cmd = Command::new(node_module_bin(outdir, "es-check"));
|
||||
cmd.arg("es2019");
|
||||
for f in files_to_check {
|
||||
cmd.arg(f);
|
||||
}
|
||||
match cmd.spawn()?.wait() {
|
||||
match spawn_cmd(&mut cmd)?.wait() {
|
||||
Ok(exit_status) => {
|
||||
if exit_status.success() {
|
||||
return Ok(());
|
||||
|
||||
@@ -182,7 +182,6 @@ fn main() {
|
||||
&ci_info,
|
||||
&librustdoc_path,
|
||||
&tools_path,
|
||||
&src_path,
|
||||
bless,
|
||||
extra_checks,
|
||||
pos_args
|
||||
|
||||
Reference in New Issue
Block a user