1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2021 The Android Open Source Project 2*6dbdd20aSAndroid Build Coastguard Worker// 3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*6dbdd20aSAndroid Build Coastguard Worker// 7*6dbdd20aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*6dbdd20aSAndroid Build Coastguard Worker// 9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License. 14*6dbdd20aSAndroid Build Coastguard Worker 15*6dbdd20aSAndroid Build Coastguard Worker'use strict'; 16*6dbdd20aSAndroid Build Coastguard Worker 17*6dbdd20aSAndroid Build Coastguard Worker// This script builds the perfetto.dev docs website. 18*6dbdd20aSAndroid Build Coastguard Worker 19*6dbdd20aSAndroid Build Coastguard Workerconst argparse = require('argparse'); 20*6dbdd20aSAndroid Build Coastguard Workerconst child_process = require('child_process'); 21*6dbdd20aSAndroid Build Coastguard Workerconst fs = require('fs'); 22*6dbdd20aSAndroid Build Coastguard Workerconst http = require('http'); 23*6dbdd20aSAndroid Build Coastguard Workerconst path = require('path'); 24*6dbdd20aSAndroid Build Coastguard Workerconst pjoin = path.join; 25*6dbdd20aSAndroid Build Coastguard Worker 26*6dbdd20aSAndroid Build Coastguard Workerconst ROOT_DIR = path.dirname(path.dirname(__dirname)); // The repo root. 27*6dbdd20aSAndroid Build Coastguard Worker 28*6dbdd20aSAndroid Build Coastguard Workerconst cfg = { 29*6dbdd20aSAndroid Build Coastguard Worker watch: false, 30*6dbdd20aSAndroid Build Coastguard Worker verbose: false, 31*6dbdd20aSAndroid Build Coastguard Worker startHttpServer: false, 32*6dbdd20aSAndroid Build Coastguard Worker 33*6dbdd20aSAndroid Build Coastguard Worker outDir: pjoin(ROOT_DIR, 'out/perfetto.dev'), 34*6dbdd20aSAndroid Build Coastguard Worker}; 35*6dbdd20aSAndroid Build Coastguard Worker 36*6dbdd20aSAndroid Build Coastguard Workerfunction main() { 37*6dbdd20aSAndroid Build Coastguard Worker const parser = new argparse.ArgumentParser(); 38*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument('--out', {help: 'Output directory'}); 39*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument('--watch', '-w', {action: 'store_true'}); 40*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument('--serve', '-s', {action: 'store_true'}); 41*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument('--verbose', '-v', {action: 'store_true'}); 42*6dbdd20aSAndroid Build Coastguard Worker 43*6dbdd20aSAndroid Build Coastguard Worker const args = parser.parse_args(); 44*6dbdd20aSAndroid Build Coastguard Worker cfg.outDir = path.resolve(ensureDir(args.out || cfg.outDir, /*clean=*/ true)); 45*6dbdd20aSAndroid Build Coastguard Worker cfg.watch = !!args.watch; 46*6dbdd20aSAndroid Build Coastguard Worker cfg.verbose = !!args.verbose; 47*6dbdd20aSAndroid Build Coastguard Worker cfg.startHttpServer = args.serve; 48*6dbdd20aSAndroid Build Coastguard Worker 49*6dbdd20aSAndroid Build Coastguard Worker // Check that deps are current before starting. 50*6dbdd20aSAndroid Build Coastguard Worker const installBuildDeps = pjoin(ROOT_DIR, 'tools/install-build-deps'); 51*6dbdd20aSAndroid Build Coastguard Worker 52*6dbdd20aSAndroid Build Coastguard Worker // --filter=nodejs --filter=pnpm --filter=gn --filter=ninja is to match what 53*6dbdd20aSAndroid Build Coastguard Worker // cloud_build_entrypoint.sh passes to install-build-deps. It doesn't bother 54*6dbdd20aSAndroid Build Coastguard Worker // installing the full toolchains because, unlike the Perfetto UI, it doesn't 55*6dbdd20aSAndroid Build Coastguard Worker // need Wasm. 56*6dbdd20aSAndroid Build Coastguard Worker const depsArgs = [ 57*6dbdd20aSAndroid Build Coastguard Worker '--check-only=/dev/null', 58*6dbdd20aSAndroid Build Coastguard Worker '--ui', 59*6dbdd20aSAndroid Build Coastguard Worker '--filter=nodejs', 60*6dbdd20aSAndroid Build Coastguard Worker '--filter=pnpm', 61*6dbdd20aSAndroid Build Coastguard Worker '--filter=gn', 62*6dbdd20aSAndroid Build Coastguard Worker '--filter=ninja' 63*6dbdd20aSAndroid Build Coastguard Worker ]; 64*6dbdd20aSAndroid Build Coastguard Worker exec(installBuildDeps, depsArgs); 65*6dbdd20aSAndroid Build Coastguard Worker 66*6dbdd20aSAndroid Build Coastguard Worker ninjaBuild(); 67*6dbdd20aSAndroid Build Coastguard Worker 68*6dbdd20aSAndroid Build Coastguard Worker if (args.watch) { 69*6dbdd20aSAndroid Build Coastguard Worker watchDir('docs'); 70*6dbdd20aSAndroid Build Coastguard Worker watchDir('infra/perfetto.dev/src/assets'); 71*6dbdd20aSAndroid Build Coastguard Worker watchDir('protos'); 72*6dbdd20aSAndroid Build Coastguard Worker watchDir('python'); 73*6dbdd20aSAndroid Build Coastguard Worker watchDir('src/trace_processor/tables'); 74*6dbdd20aSAndroid Build Coastguard Worker } 75*6dbdd20aSAndroid Build Coastguard Worker if (args.serve) { 76*6dbdd20aSAndroid Build Coastguard Worker startServer(); 77*6dbdd20aSAndroid Build Coastguard Worker } 78*6dbdd20aSAndroid Build Coastguard Worker} 79*6dbdd20aSAndroid Build Coastguard Worker 80*6dbdd20aSAndroid Build Coastguard Workerfunction ninjaBuild() { 81*6dbdd20aSAndroid Build Coastguard Worker exec( 82*6dbdd20aSAndroid Build Coastguard Worker pjoin(ROOT_DIR, 'tools/gn'), 83*6dbdd20aSAndroid Build Coastguard Worker ['gen', cfg.outDir, '--args=enable_perfetto_site=true']); 84*6dbdd20aSAndroid Build Coastguard Worker exec(pjoin(ROOT_DIR, 'tools/ninja'), ['-C', cfg.outDir, 'site']); 85*6dbdd20aSAndroid Build Coastguard Worker} 86*6dbdd20aSAndroid Build Coastguard Worker 87*6dbdd20aSAndroid Build Coastguard Workerfunction startServer() { 88*6dbdd20aSAndroid Build Coastguard Worker const port = 8082; 89*6dbdd20aSAndroid Build Coastguard Worker console.log(`Starting HTTP server on http://localhost:${port}`) 90*6dbdd20aSAndroid Build Coastguard Worker const serveDir = path.join(cfg.outDir, 'site'); 91*6dbdd20aSAndroid Build Coastguard Worker http.createServer(function(req, res) { 92*6dbdd20aSAndroid Build Coastguard Worker console.debug(req.method, req.url); 93*6dbdd20aSAndroid Build Coastguard Worker let uri = req.url.split('?', 1)[0]; 94*6dbdd20aSAndroid Build Coastguard Worker uri += uri.endsWith('/') ? 'index.html' : ''; 95*6dbdd20aSAndroid Build Coastguard Worker 96*6dbdd20aSAndroid Build Coastguard Worker // Disallow serving anything outside out directory. 97*6dbdd20aSAndroid Build Coastguard Worker const absPath = path.normalize(path.join(serveDir, uri)); 98*6dbdd20aSAndroid Build Coastguard Worker const relative = path.relative(serveDir, absPath); 99*6dbdd20aSAndroid Build Coastguard Worker if (relative.startsWith('..')) { 100*6dbdd20aSAndroid Build Coastguard Worker res.writeHead(404); 101*6dbdd20aSAndroid Build Coastguard Worker res.end(); 102*6dbdd20aSAndroid Build Coastguard Worker return; 103*6dbdd20aSAndroid Build Coastguard Worker } 104*6dbdd20aSAndroid Build Coastguard Worker 105*6dbdd20aSAndroid Build Coastguard Worker fs.readFile(absPath, function(err, data) { 106*6dbdd20aSAndroid Build Coastguard Worker if (err) { 107*6dbdd20aSAndroid Build Coastguard Worker res.writeHead(404); 108*6dbdd20aSAndroid Build Coastguard Worker res.end(JSON.stringify(err)); 109*6dbdd20aSAndroid Build Coastguard Worker return; 110*6dbdd20aSAndroid Build Coastguard Worker } 111*6dbdd20aSAndroid Build Coastguard Worker const mimeMap = { 112*6dbdd20aSAndroid Build Coastguard Worker 'css': 'text/css', 113*6dbdd20aSAndroid Build Coastguard Worker 'png': 'image/png', 114*6dbdd20aSAndroid Build Coastguard Worker 'svg': 'image/svg+xml', 115*6dbdd20aSAndroid Build Coastguard Worker 'js': 'application/javascript', 116*6dbdd20aSAndroid Build Coastguard Worker }; 117*6dbdd20aSAndroid Build Coastguard Worker const contentType = mimeMap[uri.split('.').pop()] || 'text/html'; 118*6dbdd20aSAndroid Build Coastguard Worker const head = { 119*6dbdd20aSAndroid Build Coastguard Worker 'Content-Type': contentType, 120*6dbdd20aSAndroid Build Coastguard Worker 'Content-Length': data.length, 121*6dbdd20aSAndroid Build Coastguard Worker 'Cache-Control': 'no-cache', 122*6dbdd20aSAndroid Build Coastguard Worker }; 123*6dbdd20aSAndroid Build Coastguard Worker res.writeHead(200, head); 124*6dbdd20aSAndroid Build Coastguard Worker res.end(data); 125*6dbdd20aSAndroid Build Coastguard Worker }); 126*6dbdd20aSAndroid Build Coastguard Worker }) 127*6dbdd20aSAndroid Build Coastguard Worker .listen(port, 'localhost'); 128*6dbdd20aSAndroid Build Coastguard Worker} 129*6dbdd20aSAndroid Build Coastguard Worker 130*6dbdd20aSAndroid Build Coastguard Workerfunction watchDir(dir) { 131*6dbdd20aSAndroid Build Coastguard Worker const absDir = path.isAbsolute(dir) ? dir : pjoin(ROOT_DIR, dir); 132*6dbdd20aSAndroid Build Coastguard Worker // Add a fs watch if in watch mode. 133*6dbdd20aSAndroid Build Coastguard Worker if (cfg.watch) { 134*6dbdd20aSAndroid Build Coastguard Worker fs.watch(absDir, {recursive: true}, (_eventType, filePath) => { 135*6dbdd20aSAndroid Build Coastguard Worker if (cfg.verbose) { 136*6dbdd20aSAndroid Build Coastguard Worker console.log('File change detected', _eventType, filePath); 137*6dbdd20aSAndroid Build Coastguard Worker } 138*6dbdd20aSAndroid Build Coastguard Worker ninjaBuild(); 139*6dbdd20aSAndroid Build Coastguard Worker }); 140*6dbdd20aSAndroid Build Coastguard Worker } 141*6dbdd20aSAndroid Build Coastguard Worker} 142*6dbdd20aSAndroid Build Coastguard Worker 143*6dbdd20aSAndroid Build Coastguard Workerfunction exec(cmd, args, opts) { 144*6dbdd20aSAndroid Build Coastguard Worker opts = opts || {}; 145*6dbdd20aSAndroid Build Coastguard Worker opts.stdout = opts.stdout || 'inherit'; 146*6dbdd20aSAndroid Build Coastguard Worker if (cfg.verbose) console.log(`${cmd} ${args.join(' ')}\n`); 147*6dbdd20aSAndroid Build Coastguard Worker const spwOpts = {cwd: cfg.outDir, stdio: ['ignore', opts.stdout, 'inherit']}; 148*6dbdd20aSAndroid Build Coastguard Worker const checkExitCode = (code, signal) => { 149*6dbdd20aSAndroid Build Coastguard Worker if (signal === 'SIGINT' || signal === 'SIGTERM') return; 150*6dbdd20aSAndroid Build Coastguard Worker if (code !== 0 && !opts.noErrCheck) { 151*6dbdd20aSAndroid Build Coastguard Worker console.error(`${cmd} ${args.join(' ')} failed with code ${code}`); 152*6dbdd20aSAndroid Build Coastguard Worker process.exit(1); 153*6dbdd20aSAndroid Build Coastguard Worker } 154*6dbdd20aSAndroid Build Coastguard Worker }; 155*6dbdd20aSAndroid Build Coastguard Worker const spawnRes = child_process.spawnSync(cmd, args, spwOpts); 156*6dbdd20aSAndroid Build Coastguard Worker checkExitCode(spawnRes.status, spawnRes.signal); 157*6dbdd20aSAndroid Build Coastguard Worker return spawnRes; 158*6dbdd20aSAndroid Build Coastguard Worker} 159*6dbdd20aSAndroid Build Coastguard Worker 160*6dbdd20aSAndroid Build Coastguard Workerfunction ensureDir(dirPath, clean) { 161*6dbdd20aSAndroid Build Coastguard Worker const exists = fs.existsSync(dirPath); 162*6dbdd20aSAndroid Build Coastguard Worker if (exists && clean) { 163*6dbdd20aSAndroid Build Coastguard Worker if (cfg.verbose) console.log('rm', dirPath); 164*6dbdd20aSAndroid Build Coastguard Worker fs.rmSync(dirPath, {recursive: true}); 165*6dbdd20aSAndroid Build Coastguard Worker } 166*6dbdd20aSAndroid Build Coastguard Worker if (!exists || clean) fs.mkdirSync(dirPath, {recursive: true}); 167*6dbdd20aSAndroid Build Coastguard Worker return dirPath; 168*6dbdd20aSAndroid Build Coastguard Worker} 169*6dbdd20aSAndroid Build Coastguard Worker 170*6dbdd20aSAndroid Build Coastguard Workermain(); 171