diff --git a/.gitignore b/.gitignore index 751667d..f3382e5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ _output/ .env .ipynb_checkpoints/ .pabotsuitenames -.venv/ +.venv*/ .yarn-packages/ *.doit.* *.egg-info diff --git a/atest/resources/CodeMirror.resource b/atest/resources/CodeMirror.resource index 3943acc..58a5535 100644 --- a/atest/resources/CodeMirror.resource +++ b/atest/resources/CodeMirror.resource @@ -7,7 +7,7 @@ Library JupyterLibrary *** Keywords *** Initialize CodeMirror [Documentation] Fix apparently-broken CSS/JS variable updates. - Update Globals For JupyterLab 4 + Update Globals For JupyterLab Version Set Suite Variable ${CM CSS EDITOR} ${CM CSS EDITOR} children=${TRUE} Set Suite Variable ${CM JS INSTANCE} ${CM JS INSTANCE} children=${TRUE} diff --git a/atest/resources/LabSelectors.resource b/atest/resources/LabSelectors.resource index b50def9..d398dcd 100644 --- a/atest/resources/LabSelectors.resource +++ b/atest/resources/LabSelectors.resource @@ -58,3 +58,6 @@ ${CSS_LAB_CMD_MARKDOWN_PREVIEW} [data-command="fileeditor:markdown-preview"] # lab 7 ${XP_LAB4_COLLAPSED_PANEL} //*[contains(@class, 'jp-Collapse-header-collapsed')] ${XP_LAB4_COLLAPSED_PANEL_TITLE} ${XP_LAB4_COLLAPSED_PANEL}//*[contains(@class, 'jp-Collapser-title')] + +# rfjl bugs +${CM CSS EDITOR} .CodeMirror diff --git a/atest/resources/Screenshots.resource b/atest/resources/Screenshots.resource index 227edec..82a15ed 100644 --- a/atest/resources/Screenshots.resource +++ b/atest/resources/Screenshots.resource @@ -28,8 +28,9 @@ Resume Screenshots Empty Screenshot Trash [Documentation] Clean out trash. - Run Keyword And Ignore Error - ... Remove Directory ${SCREENSHOT_TRASH} ${TRUE} + Log TODO: restore + # Run Keyword And Ignore Error + # ... Remove Directory ${SCREENSHOT_TRASH} ${TRUE} Capture Page Screenshot And Tag With Error [Documentation] Capture a screenshot if not going to the trash diff --git a/atest/resources/Server.resource b/atest/resources/Server.resource index 1a86e02..c746e67 100644 --- a/atest/resources/Server.resource +++ b/atest/resources/Server.resource @@ -73,7 +73,6 @@ Build Custom JupyterLab Args ... --port\=${port} ... --IdentityProvider.token\=${token} ... --ServerApp.base_url\=${base url} - ... --LabApp.check_for_updates_class\=jupyterlab.NeverCheckForUpdate Log ${args} RETURN @{args} diff --git a/dodo.py b/dodo.py index 0cb2929..c1dc601 100644 --- a/dodo.py +++ b/dodo.py @@ -38,6 +38,8 @@ class C: PY_VERSION = f"{sys.version_info[0]}.{sys.version_info[1]}" ROBOT_DRYRUN = "--dryrun" NYC = ["jlpm", "nyc", "report"] + HISTORY = "conda-meta/history" + CONDA_RUN = ["conda", "run", "--no-capture-output", "--prefix"] class P: @@ -127,7 +129,9 @@ class E: class B: ENV = P.ROOT / ".venv" if E.LOCAL else Path(sys.prefix) - HISTORY = [ENV / "conda-meta/history"] if E.LOCAL else [] + HISTORY = [ENV / C.HISTORY] if E.LOCAL else [] + ENV_LEGACY = P.ROOT / ".venv-legacy" if E.LOCAL else Path(sys.prefix) + HISTORY_LEGACY = ENV_LEGACY / C.HISTORY NODE_MODULES = P.ROOT / "node_modules" YARN_INTEGRITY = NODE_MODULES / ".yarn-state.yml" JS_META_TSBUILDINFO = P.JS_META / ".src.tsbuildinfo" @@ -301,15 +305,21 @@ def update_env_fragments(dest_env: Path, src_envs: typing.List[Path]): dest_env.write_text(dest_text.strip() + "\n") @staticmethod - def make_robot_tasks(extra_args=None): + def make_robot_tasks(lab_env=None, extra_args=None): + lab_env = lab_env or B.ENV extra_args = extra_args or [] name = "robot" - file_dep = [*B.HISTORY, *L.ALL_ROBOT] + file_dep = [lab_env / C.HISTORY, *L.ALL_ROBOT] if C.ROBOT_DRYRUN in extra_args: name = f"{name}:dryrun" else: file_dep += [B.PIP_FROZEN, *L.ALL_PY_SRC, *L.ALL_TS, *L.ALL_JSON] - out_dir = B.ROBOT / U.get_robot_stem(attempt=1, extra_args=extra_args) + name = f"{name}:{lab_env.name}" + out_dir = B.ROBOT / U.get_robot_stem( + lab_env=lab_env, + attempt=1, + extra_args=extra_args, + ) targets = [ out_dir / "output.xml", out_dir / "log.html", @@ -328,12 +338,12 @@ def make_robot_tasks(extra_args=None): doit.tools.config_changed({"cov": E.WITH_JS_COV, "args": E.ROBOT_ARGS}), ], "file_dep": file_dep, - "actions": [*actions, (U.run_robot_with_retries, [extra_args])], + "actions": [*actions, (U.run_robot_with_retries, [lab_env, extra_args])], "targets": targets, } @staticmethod - def run_robot_with_retries(extra_args=None): + def run_robot_with_retries(lab_env, extra_args=None): extra_args = [*(extra_args or []), *E.ROBOT_ARGS] is_dryrun = C.ROBOT_DRYRUN in extra_args @@ -350,7 +360,7 @@ def run_robot_with_retries(extra_args=None): attempt += 1 print(f"attempt {attempt} of {retries + 1}...", flush=True) start_time = time.time() - fail_count = U.run_robot(attempt=attempt, extra_args=extra_args) + fail_count = U.run_robot(lab_env, attempt=attempt, extra_args=extra_args) print( fail_count, "failed in", @@ -383,9 +393,14 @@ def run_robot_with_retries(extra_args=None): if p != final and "dry_run" not in str(p) and "pabot_results" not in str(p) ] + runner = ["python"] + + if lab_env != B.ENV: + runner = [*C.CONDA_RUN, str(lab_env), *runner] + subprocess.call( [ - "python", + *runner, "-m", "robot.rebot", "--name", @@ -408,13 +423,18 @@ def run_robot_with_retries(extra_args=None): return fail_count == 0 @staticmethod - def get_robot_stem(attempt=0, extra_args=None, browser="headlessfirefox"): + def get_robot_stem( + lab_env: Path, + attempt=0, + extra_args=None, + browser="headlessfirefox", + ): """Get the directory in B.ROBOT for this platform/app.""" extra_args = extra_args or [] browser = browser.replace("headless", "") - stem = f"{C.PLATFORM[:3].lower()}_{C.PY_VERSION}_{browser}_{attempt}" + stem = f"{C.PLATFORM[:3].lower()}_{C.PY_VERSION}_{lab_env.name}_{browser}_{attempt}" if C.ROBOT_DRYRUN in extra_args: stem = "dry_run" @@ -422,12 +442,37 @@ def get_robot_stem(attempt=0, extra_args=None, browser="headlessfirefox"): return stem @staticmethod - def run_robot(attempt=0, extra_args=None): + def prep_robot(out_dir: Path): + if out_dir.exists(): + print(f">>> trying to clean out {out_dir}", flush=True) + try: + shutil.rmtree(out_dir) + except Exception as err: + print( + f"... error, hopefully harmless: {err}", + flush=True, + ) + + if not out_dir.exists(): + print( + f">>> trying to prepare output directory: {out_dir}", + flush=True, + ) + try: + out_dir.mkdir(parents=True) + except Exception as err: + print( + f"... Error, hopefully harmless: {err}", + flush=True, + ) + + @staticmethod + def run_robot(lab_env: Path, attempt=0, extra_args=None): import lxml.etree as ET extra_args = extra_args or [] - stem = U.get_robot_stem(attempt=attempt, extra_args=extra_args) + stem = U.get_robot_stem(lab_env, attempt=attempt, extra_args=extra_args) out_dir = B.ROBOT / stem if attempt > 1: @@ -444,6 +489,10 @@ def run_robot(attempt=0, extra_args=None): *E.PABOT_ARGS, ] + if lab_env == B.ENV_LEGACY: + runner = [*C.CONDA_RUN, str(lab_env), *runner] + extra_args += ["--exclude", "app:nb"] + if C.ROBOT_DRYRUN in extra_args: runner = ["robot"] @@ -464,40 +513,12 @@ def run_robot(attempt=0, extra_args=None): *extra_args, ] - if out_dir.exists(): - print(f">>> trying to clean out {out_dir}", flush=True) - try: - shutil.rmtree(out_dir) - except Exception as err: - print( - f"... error, hopefully harmless: {err}", - flush=True, - ) - - if not out_dir.exists(): - print( - f">>> trying to prepare output directory: {out_dir}", - flush=True, - ) - try: - out_dir.mkdir(parents=True) - except Exception as err: - print( - f"... Error, hopefully harmless: {err}", - flush=True, - ) + str_args = [*map(str, [*args, P.ROBOT_SUITES])] - str_args = [ - *map( - str, - [ - *args, - P.ROBOT_SUITES, - ], - ), - ] print(">>> ", " ".join(str_args), flush=True) + U.prep_robot(out_dir) + proc = subprocess.Popen(str_args, cwd=P.ATEST) try: @@ -589,6 +610,27 @@ def task_setup(): ], } + legacy_pip = [*C.CONDA_RUN, B.ENV_LEGACY, "python", "-m", "pip"] + + yield { + "name": "conda:legacy", + "file_dep": [P.TEST_35_ENV_YAML, B.WHEEL], + "targets": [B.HISTORY_LEGACY], + "actions": [ + [ + "mamba", + "env", + "update", + "--prefix", + B.ENV_LEGACY, + "--file", + P.TEST_35_ENV_YAML, + ], + [*legacy_pip, "install", "--no-deps", "--ignore-installed", B.WHEEL], + [*legacy_pip, "check"], + ], + } + if E.LOCAL or not B.YARN_INTEGRITY.exists(): yield { "name": "yarn", @@ -802,6 +844,7 @@ def task_test(): } yield from U.make_robot_tasks() + yield from U.make_robot_tasks(lab_env=B.ENV_LEGACY) def task_lint(): @@ -917,7 +960,13 @@ def task_build(): uptodate = [doit.tools.config_changed({"WITH_JS_COV": E.WITH_JS_COV})] - ext_dep = [*P.JS_PACKAGE_JSONS, P.EXT_JS_WEBPACK, *L.ALL_CSS_SRC] + ext_dep = [ + *P.JS_PACKAGE_JSONS, + P.EXT_JS_WEBPACK, + *L.ALL_CSS_SRC, + *L.ALL_TS, + *L.ALL_CSS_SRC, + ] if E.WITH_JS_COV: ext_task = "labextension:build:cov" @@ -992,22 +1041,18 @@ def task_lite(): def task_serve(): import subprocess - def lab(): - proc = subprocess.Popen( - list( - map( - str, - [ - "jupyter", - "lab", - "--no-browser", - "--debug", - "--LanguageServerManager.autodetect=0", - ], - ), - ), - stdin=subprocess.PIPE, - ) + def lab(lab_env: Path, extra_args=None): + args = [ + *C.CONDA_RUN, + str(lab_env), + "jupyter", + "lab", + "--no-browser", + "--debug", + "--LanguageServerManager.autodetect=0", + *(extra_args or []), + ] + proc = subprocess.Popen(list(map(str, args)), stdin=subprocess.PIPE) try: proc.wait() @@ -1023,7 +1068,14 @@ def lab(): "name": "lab", "uptodate": [lambda: False], "file_dep": [B.ENV_PKG_JSON, B.PIP_FROZEN], - "actions": [doit.tools.PythonInteractiveAction(lab)], + "actions": [doit.tools.PythonInteractiveAction(lab, [B.ENV])], + } + + yield { + "name": "lab:legacy", + "uptodate": [lambda: False], + "file_dep": [B.HISTORY_LEGACY], + "actions": [doit.tools.PythonInteractiveAction(lab, [B.ENV_LEGACY])], } diff --git a/js/jupyterlab-deck/src/labcompat.ts b/js/jupyterlab-deck/src/labcompat.ts new file mode 100644 index 0000000..508c22e --- /dev/null +++ b/js/jupyterlab-deck/src/labcompat.ts @@ -0,0 +1,19 @@ +import { toArray } from '@lumino/algorithm'; +import { JSONExt } from '@lumino/coreutils'; +import type { DockPanel, TabBar, Widget } from '@lumino/widgets'; + +const { emptyArray } = JSONExt; + +export function tabBars(dockPanel: DockPanel): TabBar[] { + if (!dockPanel) { + return emptyArray as any as TabBar[]; + } + + let tabBars = dockPanel.tabBars(); + + if (Array.isArray(tabBars)) { + return tabBars; + } + + return toArray(tabBars); +} diff --git a/js/jupyterlab-deck/src/manager.ts b/js/jupyterlab-deck/src/manager.ts index 178c356..d96b768 100644 --- a/js/jupyterlab-deck/src/manager.ts +++ b/js/jupyterlab-deck/src/manager.ts @@ -10,6 +10,7 @@ import { Signal, ISignal } from '@lumino/signaling'; import { Widget, DockPanel } from '@lumino/widgets'; import { ICONS } from './icons'; +import { tabBars } from './labcompat'; import { IDeckManager, DATA, @@ -164,8 +165,8 @@ export class DeckManager implements IDeckManager { } document.body.dataset[DATA.deckMode] = DATA.presenting; if (this._dockPanel) { - for (const tabBar of this._dockPanel.tabBars()) { - tabBar.hide(); + for (const bar of tabBars(this._dockPanel)) { + bar.hide(); } } if (_labShell) { @@ -246,8 +247,8 @@ export class DeckManager implements IDeckManager { } if (_dockPanel) { - for (const tabBar of _dockPanel.tabBars()) { - tabBar.show(); + for (const bar of tabBars(_dockPanel)) { + bar.show(); } }