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