|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Copyright (c) Microsoft Corporation. |
| 3 | +# |
| 4 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +# you may not use this file except in compliance with the License. |
| 6 | +# You may obtain a copy of the License at |
| 7 | +# |
| 8 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +# |
| 10 | +# Unless required by applicable law or agreed to in writing, software |
| 11 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +# See the License for the specific language governing permissions and |
| 14 | +# limitations under the License. |
| 15 | + |
| 16 | +# Build the Playwright driver bundles from upstream source. |
| 17 | +# |
| 18 | +# This script checks out microsoft/playwright at the commit pinned in the |
| 19 | +# DRIVER_SHA file (repo root) and runs upstream's |
| 20 | +# utils/build/build-playwright-driver.sh. That script cross-builds the |
| 21 | +# per-platform bundles, which this script stages into driver/ as |
| 22 | +# playwright-<sha>-<suffix>.zip for setup.py to embed into the platform wheels. |
| 23 | +# |
| 24 | +# The pin is an immutable commit SHA (tags can be moved upstream) and lives in |
| 25 | +# the neutral DRIVER_SHA file so setup.py and CI can read it without parsing |
| 26 | +# this script. The SHA is baked into the staged bundle filenames, so the |
| 27 | +# filename doubles as the build cache key: a roll changes DRIVER_SHA, which |
| 28 | +# changes the filenames and invalidates the cache. |
| 29 | +# |
| 30 | +# A single host builds all platform bundles at once: the upstream script |
| 31 | +# downloads the matching Node.js binary for each target, so the host platform |
| 32 | +# does not constrain which bundles can be produced. |
| 33 | +# |
| 34 | +# This is intentionally a shell script (rather than language-specific code) so |
| 35 | +# the same build step can be shared across the Playwright language forks. |
| 36 | +# |
| 37 | +# Usage: scripts/build_driver.sh (reads the pin from DRIVER_SHA; no arguments) |
| 38 | + |
| 39 | +set -euo pipefail |
| 40 | + |
| 41 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 42 | +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" |
| 43 | +DRIVER_DIR="$REPO_ROOT/driver" |
| 44 | +SOURCE_DIR="$DRIVER_DIR/playwright-src" |
| 45 | +PLAYWRIGHT_REPO="https://github.com/microsoft/playwright" |
| 46 | + |
| 47 | +# The driver pin: an immutable commit in microsoft/playwright. |
| 48 | +# microsoft/playwright @ v1.60.0 |
| 49 | +EXPECTED_SHA="$(tr -d '[:space:]' < "$REPO_ROOT/DRIVER_SHA")" |
| 50 | +if [[ -z "$EXPECTED_SHA" ]]; then |
| 51 | + echo "DRIVER_SHA is empty or missing at $REPO_ROOT/DRIVER_SHA" >&2 |
| 52 | + exit 2 |
| 53 | +fi |
| 54 | + |
| 55 | +# Bundle suffixes produced by utils/build/build-playwright-driver.sh. Keep in |
| 56 | +# sync with the "zip_name" values in setup.py. |
| 57 | +SUFFIXES=(mac mac-arm64 linux linux-arm64 win32_x64 win32_arm64) |
| 58 | + |
| 59 | +bundles_present() { |
| 60 | + local suffix |
| 61 | + for suffix in "${SUFFIXES[@]}"; do |
| 62 | + [[ -f "$DRIVER_DIR/playwright-$EXPECTED_SHA-$suffix.zip" ]] || return 1 |
| 63 | + done |
| 64 | + return 0 |
| 65 | +} |
| 66 | + |
| 67 | +require_tools() { |
| 68 | + local missing=() |
| 69 | + local tool |
| 70 | + for tool in git node npm bash; do |
| 71 | + if ! command -v "$tool" >/dev/null 2>&1; then |
| 72 | + missing+=("$tool") |
| 73 | + fi |
| 74 | + done |
| 75 | + if [[ ${#missing[@]} -gt 0 ]]; then |
| 76 | + echo "Building the Playwright driver from source requires the following tools," >&2 |
| 77 | + echo "which were not found on PATH: ${missing[*]}." >&2 |
| 78 | + echo "Install Node.js (with npm), git and bash, then retry. On Windows, run the" >&2 |
| 79 | + echo "build from a bash shell (e.g. Git Bash)." >&2 |
| 80 | + exit 1 |
| 81 | + fi |
| 82 | +} |
| 83 | + |
| 84 | +checked_out_sha() { |
| 85 | + if [[ -d "$SOURCE_DIR/.git" ]]; then |
| 86 | + git -C "$SOURCE_DIR" rev-parse HEAD 2>/dev/null || true |
| 87 | + fi |
| 88 | +} |
| 89 | + |
| 90 | +clone_source() { |
| 91 | + # Reuse an existing checkout's git object store across rolls: only initialize |
| 92 | + # a fresh repo when one isn't already present, then fetch and check out the |
| 93 | + # pinned commit. This avoids re-cloning the repo (and re-running npm ci from |
| 94 | + # scratch) every time the pin changes. |
| 95 | + if [[ ! -d "$SOURCE_DIR/.git" ]]; then |
| 96 | + rm -rf "$SOURCE_DIR" |
| 97 | + mkdir -p "$SOURCE_DIR" |
| 98 | + git -C "$SOURCE_DIR" init -q |
| 99 | + git -C "$SOURCE_DIR" remote add origin "$PLAYWRIGHT_REPO" |
| 100 | + fi |
| 101 | + if [[ "$(checked_out_sha)" != "$EXPECTED_SHA" ]]; then |
| 102 | + echo "Fetching $PLAYWRIGHT_REPO at $EXPECTED_SHA" |
| 103 | + # Shallow-fetch a single commit. GitHub allows fetching an arbitrary commit |
| 104 | + # by SHA, so a full clone is unnecessary. |
| 105 | + git -C "$SOURCE_DIR" fetch --depth 1 origin "$EXPECTED_SHA" |
| 106 | + git -C "$SOURCE_DIR" checkout -q --detach FETCH_HEAD |
| 107 | + fi |
| 108 | + # Make sure we landed on exactly the pinned commit. |
| 109 | + if [[ "$(checked_out_sha)" != "$EXPECTED_SHA" ]]; then |
| 110 | + echo "Checked out commit '$(checked_out_sha)' but '$EXPECTED_SHA' was requested." >&2 |
| 111 | + exit 1 |
| 112 | + fi |
| 113 | +} |
| 114 | + |
| 115 | +build_source() { |
| 116 | + echo "Installing Playwright dependencies (npm ci)" |
| 117 | + (cd "$SOURCE_DIR" && npm ci) |
| 118 | + # Drop build outputs left over from a previously checked-out commit when the |
| 119 | + # checkout is reused across rolls (lib/ dirs are gitignored, so switching |
| 120 | + # commits doesn't clear them). |
| 121 | + echo "Cleaning previous build outputs (npm run clean)" |
| 122 | + (cd "$SOURCE_DIR" && npm run clean) |
| 123 | + echo "Building Playwright (npm run build)" |
| 124 | + (cd "$SOURCE_DIR" && npm run build) |
| 125 | + echo "Building driver bundles" |
| 126 | + (cd "$SOURCE_DIR" && bash utils/build/build-playwright-driver.sh) |
| 127 | +} |
| 128 | + |
| 129 | +copy_bundles() { |
| 130 | + local output_dir="$SOURCE_DIR/utils/build/output" |
| 131 | + # The output dir also holds build intermediates (downloaded Node.js binaries, |
| 132 | + # tgz archives, extracted package dirs), so copy only the bundles. Upstream |
| 133 | + # names them playwright-<version>-<suffix>.zip; restage each one with the pin |
| 134 | + # SHA in the name so the filename doubles as the build cache key. |
| 135 | + local suffix matches |
| 136 | + for suffix in "${SUFFIXES[@]}"; do |
| 137 | + matches=("$output_dir"/playwright-*-"$suffix".zip) |
| 138 | + if [[ ! -f "${matches[0]}" ]]; then |
| 139 | + echo "Expected driver bundle for '$suffix' was not produced in $output_dir" >&2 |
| 140 | + exit 1 |
| 141 | + fi |
| 142 | + cp "${matches[0]}" "$DRIVER_DIR/playwright-$EXPECTED_SHA-$suffix.zip" |
| 143 | + done |
| 144 | +} |
| 145 | + |
| 146 | +# Fast path: the bundles for this exact pin are already staged, so there is |
| 147 | +# nothing to (re)build. This keeps repeat invocations cheap and lets consumers |
| 148 | +# that only downloaded the prebuilt bundles skip the build entirely (no Node). |
| 149 | +if bundles_present; then |
| 150 | + echo "Driver bundles for $EXPECTED_SHA already present in $DRIVER_DIR; skipping build." |
| 151 | + exit 0 |
| 152 | +fi |
| 153 | + |
| 154 | +require_tools |
| 155 | +clone_source |
| 156 | +build_source |
| 157 | +copy_bundles |
0 commit comments