1# Copyright (C) 2018 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import("../gn/perfetto.gni") 16import("../gn/perfetto_check_build_deps.gni") 17import("../gn/wasm.gni") 18import("../protos/perfetto/trace_processor/proto_files.gni") 19 20# Prevent that this file is accidentally included in embedder builds. 21assert(enable_perfetto_ui) 22 23ui_dir = "$root_build_dir/ui" 24chrome_extension_dir = "$root_build_dir/chrome_extension" 25ui_gen_dir = "$target_out_dir/gen" 26nodejs_root = "../buildtools/nodejs" 27nodejs_bin = rebase_path("$nodejs_root/bin", root_build_dir) 28 29# +----------------------------------------------------------------------------+ 30# | The outer "ui" target to just ninja -C out/xxx ui | 31# +----------------------------------------------------------------------------+ 32 33group("ui") { 34 deps = [ 35 ":chrome_extension_assets_dist", 36 ":chrome_extension_bundle_dist", 37 ":dist", 38 ":gen_dist_file_map", 39 ":service_worker_bundle_dist", 40 ":test_scripts", 41 42 # IMPORTANT: Only add deps here if they are NOT part of the production UI 43 # (e.g., tests, extensions, ...). Any UI dep should go in the 44 # |ui_dist_targets| list below. The only exception is the service worker 45 # target, that depends on that list. 46 ] 47} 48 49# The list of targets that produces dist/ files for the UI. This list is used 50# also by the gen_dist_file_map to generate the map of hashes of all UI files, 51# which is turn used by the service worker code for the offline caching. 52ui_dist_targets = [ 53 ":assets_dist", 54 ":catapult_dist", 55 ":controller_bundle_dist", 56 ":engine_bundle_dist", 57 ":frontend_bundle_dist", 58 ":index_dist", 59 ":scss", 60 ":typefaces_dist", 61 ":wasm_dist", 62] 63 64# Buils the ui, but not service worker, tests and extensions. 65group("dist") { 66 deps = ui_dist_targets 67} 68 69# A minimal page to profile the WASM engine without the all UI. 70group("query") { 71 deps = [ 72 ":query_bundle_dist", 73 ":query_dist", 74 ":ui", 75 ] 76} 77 78# +----------------------------------------------------------------------------+ 79# | Template used to run node binaries using the hermetic node toolchain. | 80# +----------------------------------------------------------------------------+ 81template("node_bin") { 82 action(target_name) { 83 forward_variables_from(invoker, 84 [ 85 "inputs", 86 "outputs", 87 "depfile", 88 ]) 89 deps = [ ":node_modules" ] 90 if (defined(invoker.deps)) { 91 deps += invoker.deps 92 } 93 script = "../gn/standalone/build_tool_wrapper.py" 94 _node_cmd = invoker.node_cmd 95 args = [] 96 if (defined(invoker.suppress_stdout) && invoker.suppress_stdout) { 97 args += [ "--suppress_stdout" ] 98 } 99 if (defined(invoker.suppress_stderr) && invoker.suppress_stderr) { 100 args += [ "--suppress_stderr" ] 101 } 102 args += [ 103 "--path=$nodejs_bin", 104 "node", 105 rebase_path("node_modules/.bin/$_node_cmd", root_build_dir), 106 ] + invoker.args 107 } 108} 109 110# +----------------------------------------------------------------------------+ 111# | Template for "sorcery" the source map resolver. | 112# +----------------------------------------------------------------------------+ 113template("sorcery") { 114 node_bin(target_name) { 115 assert(defined(invoker.input)) 116 assert(defined(invoker.output)) 117 forward_variables_from(invoker, [ "deps" ]) 118 inputs = [ invoker.input ] 119 outputs = [ 120 invoker.output, 121 invoker.output + ".map", 122 ] 123 node_cmd = "sorcery" 124 args = [ 125 "-i", 126 rebase_path(invoker.input, root_build_dir), 127 "-o", 128 rebase_path(invoker.output, root_build_dir), 129 ] 130 } 131} 132 133# +----------------------------------------------------------------------------+ 134# | Template for bundling js | 135# +----------------------------------------------------------------------------+ 136template("bundle") { 137 node_bin(target_name) { 138 assert(defined(invoker.input)) 139 assert(defined(invoker.output)) 140 forward_variables_from(invoker, [ "deps" ]) 141 inputs = [ 142 invoker.input, 143 "rollup.config.js", 144 ] 145 outputs = [ 146 invoker.output, 147 invoker.output + ".map", 148 ] 149 node_cmd = "rollup" 150 args = [ 151 "-c", 152 rebase_path("rollup.config.js", root_build_dir), 153 rebase_path(invoker.input, root_build_dir), 154 "-o", 155 rebase_path(invoker.output, root_build_dir), 156 "-f", 157 "iife", 158 "-m", 159 "--silent", 160 ] 161 } 162} 163 164# +----------------------------------------------------------------------------+ 165# | Bundles all *.js files together resolving CommonJS require() deps. | 166# +----------------------------------------------------------------------------+ 167 168# Bundle together all js sources into a bundle.js file, that will ultimately be 169# included by the .html files. 170 171bundle("frontend_bundle") { 172 deps = [ ":transpile_all_ts" ] 173 input = "$target_out_dir/frontend/index.js" 174 output = "$target_out_dir/frontend_bundle.js" 175} 176 177bundle("chrome_extension_bundle") { 178 deps = [ ":transpile_all_ts" ] 179 input = "$target_out_dir/chrome_extension/index.js" 180 output = "$target_out_dir/chrome_extension_bundle.js" 181} 182 183bundle("controller_bundle") { 184 deps = [ ":transpile_all_ts" ] 185 input = "$target_out_dir/controller/index.js" 186 output = "$target_out_dir/controller_bundle.js" 187} 188 189bundle("engine_bundle") { 190 deps = [ ":transpile_all_ts" ] 191 input = "$target_out_dir/engine/index.js" 192 output = "$target_out_dir/engine_bundle.js" 193} 194 195bundle("service_worker_bundle") { 196 deps = [ ":transpile_service_worker_ts" ] 197 input = "$target_out_dir/service_worker/service_worker.js" 198 output = "$target_out_dir/service_worker.js" 199} 200 201bundle("query_bundle") { 202 deps = [ ":transpile_all_ts" ] 203 input = "$target_out_dir/query/index.js" 204 output = "$target_out_dir/query_bundle.js" 205} 206 207# +----------------------------------------------------------------------------+ 208# | Protobuf: gen rules to create .js and .d.ts files from protos. | 209# +----------------------------------------------------------------------------+ 210node_bin("protos_to_js") { 211 inputs = [] 212 foreach(proto, trace_processor_protos) { 213 inputs += [ "../protos/perfetto/trace_processor/$proto.proto" ] 214 } 215 inputs += [ 216 "../protos/perfetto/config/perfetto_config.proto", 217 "../protos/perfetto/ipc/consumer_port.proto", 218 "../protos/perfetto/ipc/wire_protocol.proto", 219 ] 220 outputs = [ "$ui_gen_dir/protos.js" ] 221 node_cmd = "pbjs" 222 args = [ 223 "-t", 224 "static-module", 225 "-w", 226 "commonjs", 227 "-p", 228 rebase_path("..", root_build_dir), 229 "-o", 230 rebase_path(outputs[0], root_build_dir), 231 ] + rebase_path(inputs, root_build_dir) 232} 233 234# Protobuf.js requires to first generate .js files from the .proto and then 235# create .ts definitions for them. 236node_bin("protos_to_ts") { 237 deps = [ ":protos_to_js" ] 238 inputs = [ "$ui_gen_dir/protos.js" ] 239 outputs = [ "$ui_gen_dir/protos.d.ts" ] 240 node_cmd = "pbts" 241 args = [ 242 "-p", 243 rebase_path("..", root_build_dir), 244 "-o", 245 rebase_path(outputs[0], root_build_dir), 246 rebase_path(inputs[0], root_build_dir), 247 ] 248} 249 250# +----------------------------------------------------------------------------+ 251# | TypeScript: transpiles all *.ts into .js | 252# +----------------------------------------------------------------------------+ 253 254# Builds all .ts sources in the repo under |src|. 255node_bin("transpile_all_ts") { 256 deps = [ 257 ":dist_symlink", 258 ":protos_to_ts", 259 ":wasm_gen", 260 ] 261 inputs = [ "tsconfig.json" ] 262 outputs = [ 263 "$target_out_dir/frontend/index.js", 264 "$target_out_dir/engine/index.js", 265 "$target_out_dir/controller/index.js", 266 "$target_out_dir/query/index.js", 267 "$target_out_dir/chrome_extension/index.js", 268 ] 269 270 depfile = root_out_dir + "/tsc.d" 271 exec_script("../gn/standalone/glob.py", 272 [ 273 "--root=" + rebase_path(".", root_build_dir), 274 "--filter=*.ts", 275 "--exclude=node_modules", 276 "--exclude=dist", 277 "--exclude=service_worker", 278 "--deps=obj/ui/frontend/index.js", 279 "--output=" + rebase_path(depfile), 280 ], 281 "") 282 283 node_cmd = "tsc" 284 args = [ 285 "--project", 286 rebase_path(".", root_build_dir), 287 "--outDir", 288 rebase_path(target_out_dir, root_build_dir), 289 ] 290} 291 292node_bin("transpile_service_worker_ts") { 293 deps = [ 294 ":dist_symlink", 295 ":gen_dist_file_map", 296 ] 297 inputs = [ 298 "tsconfig.json", 299 "src/service_worker/service_worker.ts", 300 ] 301 outputs = [ "$target_out_dir/service_worker/service_worker.js" ] 302 303 node_cmd = "tsc" 304 args = [ 305 "--project", 306 rebase_path("src/service_worker", root_build_dir), 307 "--outDir", 308 rebase_path(target_out_dir, root_build_dir), 309 ] 310} 311 312# +----------------------------------------------------------------------------+ 313# | Build css. | 314# +----------------------------------------------------------------------------+ 315 316scss_root = "src/assets/perfetto.scss" 317scss_srcs = [ 318 "src/assets/typefaces.scss", 319 "src/assets/sidebar.scss", 320 "src/assets/topbar.scss", 321 "src/assets/record.scss", 322 "src/assets/common.scss", 323 "src/assets/modal.scss", 324 "src/assets/details.scss", 325] 326 327# Build css. 328node_bin("scss") { 329 deps = [ ":dist_symlink" ] 330 inputs = [ scss_root ] + scss_srcs 331 outputs = [ "$ui_dir/perfetto.css" ] 332 333 node_cmd = "node-sass" 334 args = [ 335 "--quiet", 336 rebase_path(scss_root, root_build_dir), 337 rebase_path(outputs[0], root_build_dir), 338 ] 339} 340 341# +----------------------------------------------------------------------------+ 342# | Copy rules: create the final output directory. | 343# +----------------------------------------------------------------------------+ 344copy("index_dist") { 345 sources = [ "index.html" ] 346 outputs = [ "$ui_dir/index.html" ] 347} 348 349copy("typefaces_dist") { 350 sources = [ 351 "../buildtools/typefaces/MaterialIcons.woff2", 352 "../buildtools/typefaces/Raleway-Regular.woff2", 353 "../buildtools/typefaces/Raleway-Thin.woff2", 354 "../buildtools/typefaces/RobotoCondensed-Light.woff2", 355 "../buildtools/typefaces/RobotoCondensed-Regular.woff2", 356 "../buildtools/typefaces/RobotoMono-Regular.woff2", 357 ] 358 359 outputs = [ "$ui_dir/assets/{{source_file_part}}" ] 360} 361 362copy("query_dist") { 363 sources = [ "query.html" ] 364 outputs = [ "$ui_dir/query.html" ] 365} 366 367copy("assets_dist") { 368 sources = [ 369 "src/assets/brand.png", 370 "src/assets/favicon.png", 371 "src/assets/logo-3d.png", 372 "src/assets/rec_atrace.png", 373 "src/assets/rec_battery_counters.png", 374 "src/assets/rec_board_voltage.png", 375 "src/assets/rec_cpu_coarse.png", 376 "src/assets/rec_cpu_fine.png", 377 "src/assets/rec_cpu_freq.png", 378 "src/assets/rec_cpu_voltage.png", 379 "src/assets/rec_cpu_wakeup.png", 380 "src/assets/rec_ftrace.png", 381 "src/assets/rec_java_heap_dump.png", 382 "src/assets/rec_lmk.png", 383 "src/assets/rec_logcat.png", 384 "src/assets/rec_long_trace.png", 385 "src/assets/rec_mem_hifreq.png", 386 "src/assets/rec_meminfo.png", 387 "src/assets/rec_native_heap_profiler.png", 388 "src/assets/rec_one_shot.png", 389 "src/assets/rec_ps_stats.png", 390 "src/assets/rec_ring_buf.png", 391 "src/assets/rec_vmstat.png", 392 ] + [ scss_root ] + scss_srcs 393 outputs = [ "$ui_dir/assets/{{source_file_part}}" ] 394} 395copy("chrome_extension_assets_dist") { 396 sources = [ 397 "src/assets/logo-128.png", 398 "src/chrome_extension/manifest.json", 399 ] 400 outputs = [ "$chrome_extension_dir/{{source_file_part}}" ] 401} 402 403sorcery("frontend_bundle_dist") { 404 deps = [ ":frontend_bundle" ] 405 input = "$target_out_dir/frontend_bundle.js" 406 output = "$ui_dir/frontend_bundle.js" 407} 408 409sorcery("chrome_extension_bundle_dist") { 410 deps = [ ":chrome_extension_bundle" ] 411 input = "$target_out_dir/chrome_extension_bundle.js" 412 output = "$chrome_extension_dir/chrome_extension_bundle.js" 413} 414 415sorcery("controller_bundle_dist") { 416 deps = [ ":controller_bundle" ] 417 input = "$target_out_dir/controller_bundle.js" 418 output = "$ui_dir/controller_bundle.js" 419} 420 421sorcery("engine_bundle_dist") { 422 deps = [ ":engine_bundle" ] 423 input = "$target_out_dir/engine_bundle.js" 424 output = "$ui_dir/engine_bundle.js" 425} 426 427sorcery("service_worker_bundle_dist") { 428 deps = [ ":service_worker_bundle" ] 429 input = "$target_out_dir/service_worker.js" 430 output = "$ui_dir/service_worker.js" 431} 432 433sorcery("query_bundle_dist") { 434 deps = [ ":query_bundle" ] 435 input = "$target_out_dir/query_bundle.js" 436 output = "$ui_dir/query_bundle.js" 437} 438 439copy("wasm_dist") { 440 deps = [ 441 "//src/trace_processor:trace_processor.wasm($wasm_toolchain)", 442 "//tools/trace_to_text:trace_to_text.wasm($wasm_toolchain)", 443 ] 444 sources = [ 445 "$root_build_dir/wasm/trace_processor.wasm", 446 "$root_build_dir/wasm/trace_to_text.wasm", 447 ] 448 outputs = [ "$ui_dir/{{source_file_part}}" ] 449} 450 451copy("wasm_gen") { 452 deps = [ 453 ":dist_symlink", 454 455 # trace_processor 456 "//src/trace_processor:trace_processor.d.ts($wasm_toolchain)", 457 "//src/trace_processor:trace_processor.js($wasm_toolchain)", 458 "//src/trace_processor:trace_processor.wasm($wasm_toolchain)", 459 460 # trace_to_text 461 "//tools/trace_to_text:trace_to_text.d.ts($wasm_toolchain)", 462 "//tools/trace_to_text:trace_to_text.js($wasm_toolchain)", 463 "//tools/trace_to_text:trace_to_text.wasm($wasm_toolchain)", 464 ] 465 sources = [ 466 # trace_processor 467 "$root_build_dir/wasm/trace_processor.d.ts", 468 "$root_build_dir/wasm/trace_processor.js", 469 "$root_build_dir/wasm/trace_processor.wasm", 470 471 # trace_to_text 472 "$root_build_dir/wasm/trace_to_text.d.ts", 473 "$root_build_dir/wasm/trace_to_text.js", 474 "$root_build_dir/wasm/trace_to_text.wasm", 475 ] 476 if (is_debug) { 477 sources += [ 478 "$root_build_dir/wasm/trace_processor.wasm.map", 479 "$root_build_dir/wasm/trace_to_text.wasm.map", 480 ] 481 } 482 outputs = [ "$ui_gen_dir/{{source_file_part}}" ] 483} 484 485# Copy over the vulcanized legacy trace viewer. 486copy("catapult_dist") { 487 sources = [ 488 "../buildtools/catapult_trace_viewer/catapult_trace_viewer.html", 489 "../buildtools/catapult_trace_viewer/catapult_trace_viewer.js", 490 ] 491 outputs = [ "$ui_dir/assets/{{source_file_part}}" ] 492} 493 494# +----------------------------------------------------------------------------+ 495# | Node JS: Creates a symlink in the out directory to node_modules. | 496# +----------------------------------------------------------------------------+ 497 498perfetto_check_build_deps("check_ui_deps") { 499 args = [ "--ui" ] 500 inputs = [ "package-lock.json" ] 501} 502 503# Creates a symlink from out/xxx/ui/node_modules -> ../../../ui/node_modules. 504# This allows to run rollup and other node tools from the out/xxx directory. 505action("node_modules_symlink") { 506 deps = [ ":check_ui_deps" ] 507 script = "../gn/standalone/build_tool_wrapper.py" 508 stamp_file = "$target_out_dir/.$target_name.stamp" 509 args = [ 510 "--stamp", 511 rebase_path(stamp_file, root_build_dir), 512 "/bin/ln", 513 "-fns", 514 rebase_path("node_modules", target_out_dir), 515 rebase_path("$target_out_dir/node_modules", root_build_dir), 516 ] 517 outputs = [ stamp_file ] 518} 519 520group("node_modules") { 521 deps = [ ":node_modules_symlink" ] 522} 523 524# Creates a symlink from //ui/dist -> ../../out/xxx/ui. Used only for 525# autocompletion in IDEs. The problem this is solving is that in tsconfig.json 526# we can't possibly know the path to ../../out/xxx for outDir. Instead, we set 527# outDir to "./dist" and create a symlink on the first build. 528action("dist_symlink") { 529 script = "../gn/standalone/build_tool_wrapper.py" 530 stamp_file = "$target_out_dir/.$target_name.stamp" 531 args = [ 532 "--stamp", 533 rebase_path(stamp_file, root_build_dir), 534 "/bin/ln", 535 "-fns", 536 rebase_path(target_out_dir, "."), 537 rebase_path("dist", root_build_dir), 538 ] 539 inputs = [ "$root_build_dir" ] 540 outputs = [ stamp_file ] 541} 542 543group("test_scripts") { 544 deps = [ 545 ":copy_tests_script", 546 ":copy_unittests_script", 547 ] 548} 549 550copy("copy_unittests_script") { 551 sources = [ "config/ui_unittests_template" ] 552 outputs = [ "$root_build_dir/ui_unittests" ] 553} 554 555copy("copy_tests_script") { 556 sources = [ "config/ui_tests_template" ] 557 outputs = [ "$root_build_dir/ui_tests" ] 558} 559 560# This target generates an map containing all the UI subresources and their 561# hashes. This is used by the service worker code for offline caching. 562# This taarget needs to be kept at the end of the BUILD.gn file, because of the 563# get_target_outputs() call (fails otherwise due to GN's evaluation order). 564action("gen_dist_file_map") { 565 out_file_path = "$ui_gen_dir/dist_file_map.ts" 566 567 dist_files = [] 568 foreach(target, ui_dist_targets) { 569 foreach(dist_file, get_target_outputs(target)) { 570 dist_files += [ rebase_path(dist_file, root_build_dir) ] 571 } 572 } 573 deps = [ ":dist" ] 574 script = "../gn/standalone/write_ui_dist_file_map.py" 575 inputs = [] 576 outputs = [ out_file_path ] 577 args = [ 578 "--out", 579 rebase_path(out_file_path, root_build_dir), 580 "--strip", 581 rebase_path(ui_dir, root_build_dir), 582 ] + dist_files 583} 584