# Copyright (C) 2022 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import("../../gn/perfetto.gni")

# Prevent that this file is accidentally included in embedder builds.
assert(enable_perfetto_site)

nodejs_bin = rebase_path("../../tools/node", root_build_dir)

# The destination directory where the website will be built. GN pollutes
# root_out_dir with all sorts of files, so we use a subdirectory.
perfetto_website_out_dir = "$root_out_dir/site"

# The directory containing all the markdown sources for the docs.
src_doc_dir = "../../docs"

# The site minus the search index, so gen_search_index can depend on this and
# run last -- once every generated .md exists on disk for it to glob.
group("site_content") {
  deps = [
    ":all_mdfiles",
    ":assets",
    ":gen_index",
    ":gen_sql_stats_html",
    ":gen_sql_tables_html",
    ":gen_stdlib_docs_html",
    ":gen_toc",
    ":gen_trace_config_proto",
    ":gen_trace_packet_proto",
    ":node_assets",
    ":readme",
    ":style_scss",
  ]
}

group("site") {
  deps = [
    ":gen_search_index",
    ":site_content",
  ]
}

# Runs a nodejs script using the hermetic node toolchain.
# Args:
# * script: The .js script to execute
# * inputs
# * outputs
# * deps
# * depfile
template("nodejs_script") {
  assert(defined(invoker.script), "Need script in $target_name")

  action(target_name) {
    forward_variables_from(invoker,
                           [
                             "outputs",
                             "depfile",
                           ])
    deps = [ ":node_modules" ]
    if (defined(invoker.deps)) {
      deps += invoker.deps
    }
    script = "../../gn/standalone/build_tool_wrapper.py"
    inputs = [ invoker.script ]
    inputs += invoker.inputs
    args = [
      nodejs_bin,
      rebase_path(invoker.script, root_build_dir),
    ]
    args += invoker.args
  }
}

# Installs the node modules specified in package.json
action("node_modules") {
  script = "../../gn/standalone/build_tool_wrapper.py"
  stamp_file = "$target_out_dir/.$target_name.stamp"
  cur_dir = rebase_path(".", root_build_dir)
  args = [
    "--stamp",
    rebase_path(stamp_file, root_build_dir),
    "--chdir=$cur_dir",
    rebase_path("../../tools/pnpm", root_build_dir),
    "install",
    "--shamefully-hoist",
    "--frozen-lockfile",
  ]
  inputs = [
    "../../tools/npm",
    "package.json",
    "pnpm-lock.yaml",
  ]
  outputs = [ stamp_file ]
}

# Renders a markdown file into html.
# Args:
# * markdown: Optional. The source markdown file
# * html_template: Optional. The html template to use
# * out_html: The generated html, relative to `perfetto_website_out_dir`
# * deps
template("md_to_html") {
  assert(defined(invoker.out_html), "Need out_html in $target_name")
  assert(defined(invoker.html_template) || defined(invoker.markdown),
         "Need html_template or markdown in $target_name")
  nodejs_script(target_name) {
    forward_variables_from(invoker, [ "deps" ])
    script = "src/markdown_render.js"
    inputs = []
    if (defined(invoker.markdown)) {
      inputs += [ invoker.markdown ]
    }
    depfile = "${target_gen_dir}/$target_name.d"
    if (defined(invoker.html_template)) {
      inputs += [ invoker.html_template ]
    }
    outputs = [ "${perfetto_website_out_dir}/${invoker.out_html}" ]
    args = [
      "--odir",
      rebase_path(perfetto_website_out_dir, root_build_dir),
      "-o",
      rebase_path("${perfetto_website_out_dir}/${invoker.out_html}",
                  root_build_dir),
      "--depfile",
      rebase_path(depfile, root_build_dir),
    ]
    if (defined(invoker.markdown)) {
      args += [
        "-i",
        rebase_path(invoker.markdown, root_build_dir),
      ]
    }
    if (defined(invoker.html_template)) {
      args += [
        "-t",
        rebase_path(invoker.html_template, root_build_dir),
      ]
    }
  }
}

md_to_html("gen_toc") {
  markdown = "${src_doc_dir}/toc.md"
  out_html = "docs/_nav.html"
}

md_to_html("gen_index") {
  html_template = "src/template_index.html"
  deps = [ ":gen_toc" ]
  out_html = "index.html"
}

nodejs_script("style_scss") {
  script = "node_modules/sass/sass.js"
  input = "src/assets/style.scss"
  inputs = [ input ]
  output = "${perfetto_website_out_dir}/assets/style.css"
  outputs = [ output ]
  args = [
    "--quiet",
    rebase_path(input, root_build_dir),
    rebase_path(output, root_build_dir),
  ]
  deps = [ ":node_modules" ]
}

sql_stats_md = "${target_gen_dir}/sql-stats.md"

nodejs_script("gen_sql_stats_md") {
  script = "src/gen_stats_reference.js"
  input = "../../src/trace_processor/storage/stats.h"
  inputs = [ input ]
  outputs = [ sql_stats_md ]
  args = [
    "-i",
    rebase_path(input, root_build_dir),
    "-o",
    rebase_path(sql_stats_md, root_build_dir),
  ]
}

md_to_html("gen_sql_stats_html") {
  markdown = sql_stats_md
  html_template = "src/template_markdown.html"
  deps = [
    ":gen_sql_stats_md",
    ":gen_toc",
  ]
  out_html = "docs/analysis/sql-stats"
}

# Generates a html reference for a proto
# Args:
# * proto: The path to a .proto file
# * message_name: The proto message name
# * out_html
template("proto_reference") {
  sql_stats_md = "${target_gen_dir}/${target_name}.md"
  nodejs_script("${target_name}_md") {
    script = "src/gen_proto_reference.js"
    inputs = [ invoker.proto ]
    outputs = [ sql_stats_md ]
    args = [
      "-i",
      rebase_path(invoker.proto, root_build_dir),
      "-p",
      invoker.message_name,
      "-o",
      rebase_path(sql_stats_md, root_build_dir),
    ]
  }

  md_to_html(target_name) {
    markdown = sql_stats_md
    html_template = "src/template_markdown.html"
    deps = [
      ":${target_name}_md",
      ":gen_toc",
    ]
    out_html = invoker.out_html
  }
}

proto_reference("gen_trace_config_proto") {
  proto = "../../protos/perfetto/config/trace_config.proto"
  message_name = "perfetto.protos.TraceConfig"
  out_html = "docs/reference/trace-config-proto"
}

proto_reference("gen_trace_packet_proto") {
  proto = "../../protos/perfetto/trace/trace_packet.proto"
  message_name = "perfetto.protos.TracePacket"
  out_html = "docs/reference/trace-packet-proto"
}

# WARNING: this does globbing at generation time. Incremental builds are not
# going to work properly if files are added/removed. `gn gen` needs to be
# rerun.
sql_tables =
    exec_script("../../gn/standalone/glob.py",
                [
                  "--root=" + rebase_path("../../src/trace_processor/tables",
                                          root_build_dir),
                  "--filter=*.h",
                ],
                "list lines")

src_sql_tables = []

foreach(i, sql_tables) {
  src_sql_tables += [ rebase_path(i, ".", root_build_dir) ]
}

sql_tables_md = "${target_gen_dir}/sql-tables.md"
stdlib_docs_md = "${target_gen_dir}/stdlib_docs.md"

action("gen_stdlib_docs_md") {
  script = "src/gen_stdlib_docs_md.py"
  label_info = get_label_info(
          "../../src/trace_processor/perfetto_sql/stdlib:stdlib_json_docs",
          "target_gen_dir")
  absolute_input_path = label_info + "/stdlib_docs.json"
  deps = [
    "../../python:trace_processor_stdlib_docs",
    "../../src/trace_processor/perfetto_sql/stdlib:stdlib_json_docs",
  ]
  outputs = [ stdlib_docs_md ]
  args = [
    "--input",
    rebase_path(absolute_input_path, root_build_dir),
    "--output",
    rebase_path(stdlib_docs_md, root_build_dir),
  ]
}

md_to_html("gen_stdlib_docs_html") {
  markdown = stdlib_docs_md
  html_template = "src/template_markdown.html"
  deps = [
    ":gen_stdlib_docs_md",
    ":gen_toc",
  ]
  out_html = "docs/analysis/stdlib-docs"
}

nodejs_script("gen_sql_tables_md") {
  python_label = "../../src/trace_processor/tables:tables_python_docs"
  python_docs_json = get_label_info(python_label, "target_gen_dir") + "/" +
                     get_label_info(python_label, "name") + ".json"

  script = "src/gen_sql_tables_reference.js"
  inputs = src_sql_tables
  deps = [ python_label ]
  outputs = [ sql_tables_md ]
  args = [
    "-o",
    rebase_path(sql_tables_md, root_build_dir),
  ]
  foreach(file, src_sql_tables) {
    args += [
      "-i",
      rebase_path(file, root_build_dir),
    ]
  }
  args += [
    "-j",
    rebase_path(python_docs_json, root_build_dir),
  ]
}

md_to_html("gen_sql_tables_html") {
  markdown = sql_tables_md
  html_template = "src/template_markdown.html"
  deps = [
    ":gen_sql_tables_md",
    ":gen_toc",
  ]
  out_html = "docs/analysis/sql-tables"
}

md_to_html("readme") {
  markdown = "${src_doc_dir}/README.md"
  html_template = "src/template_markdown.html"
  out_html = "docs/index.html"
  deps = [ ":gen_toc" ]
}

# WARNING: this does globbing at generation time. Incremental builds are not
# going to work properly if files are added/removed. `gn gen` needs to be
# rerun.
mdfiles = exec_script("../../gn/standalone/glob.py",
                      [
                        "--root=" + rebase_path(src_doc_dir, root_build_dir),
                        "--filter=*.md",
                      ],
                      "list lines")

mdfiles -= [
  rebase_path("../../docs/README.md", root_build_dir),
  rebase_path("../../docs/toc.md", root_build_dir),
]

mdtargets = []

foreach(source, mdfiles) {
  filename = rebase_path(string_replace(source, ".md", ""),
                         rebase_path("../../docs", root_build_dir))
  md_to_html("mdfile_${filename}") {
    markdown = rebase_path(source, ".", root_build_dir)
    html_template = "src/template_markdown.html"
    out_html = "docs/${filename}"
    deps = [ ":gen_toc" ]
  }
  mdtargets += [ ":mdfile_${filename}" ]
}

# Files which have been removed/renamed/moved and now have HTTP redirections in
# src/assets/script.js
removed_renamed_moved_files = [
  "analysis/common-queries.md",
  "analysis/pivot-tables.md",
  "case-studies/android-boot-tracing.md",
  "case-studies/android-outofmemoryerror.md",
  "contributing/embedding.md",
  "contributing/perfetto-in-the-press.md",
  "contributing/ui-development.md",
  "quickstart/android-tracing.md",
  "quickstart/callstack-sampling.md",
  "quickstart/chrome-tracing.md",
  "quickstart/heap-profiling.md",
  "quickstart/linux-tracing.md",
  "quickstart/trace-analysis.md",
]

foreach(source, removed_renamed_moved_files) {
  filename = string_replace(source, ".md", "")
  md_to_html("mdfile_${filename}") {
    markdown = "src/empty.md"
    html_template = "src/template_markdown.html"
    out_html = "docs/${filename}"
    deps = [ ":gen_toc" ]
  }
  mdtargets += [ ":mdfile_${filename}" ]
}

group("all_mdfiles") {
  deps = mdtargets
}

# Builds assets/search_index.json.gz, the client-side full-text search index
# consumed by setupSearch() in src/assets/script.js. Hand-written docs are
# indexed in full; the huge auto-generated reference pages (SQL tables, stdlib,
# protos) are indexed by title + headings only to keep the shipped index small.
gen_search_index_out = "${perfetto_website_out_dir}/assets/search_index.json.gz"

nodejs_script("gen_search_index") {
  script = "src/gen_search_index.js"
  inputs = [
    "src/md_utils.js",  # require()d by the script for anchor ids.
    "${src_doc_dir}/toc.md",  # Nav labels indexed at title weight.
  ]
  outputs = [ gen_search_index_out ]

  # Run last, so the glob below sees every generated .md. Caveat: a generated .md
  # isn't a declared input, so editing its source needs a clean build to
  # re-index (the deploy always builds clean).
  deps = [ ":site_content" ]
  args = [
    "--out",
    rebase_path(gen_search_index_out, root_build_dir),
    "--doc-root",
    rebase_path(src_doc_dir, root_build_dir),
    "--toc",
    rebase_path("${src_doc_dir}/toc.md", root_build_dir),

    # Generated reference pages: the script globs this dir for *.md and takes
    # each page's URL from its toc.md .autogen entry.
    "--gen-dir",
    rebase_path(target_gen_dir, root_build_dir),
  ]

  # Full-text: every hand-written doc (from the same glob as md_to_html) plus the
  # landing page (README.md -> /docs/). Inputs, so editing a doc re-indexes.
  full_mdfiles = mdfiles
  full_mdfiles += [ rebase_path("../../docs/README.md", root_build_dir) ]
  foreach(source, full_mdfiles) {
    inputs += [ rebase_path(source, ".", root_build_dir) ]
    args += [
      "--full",
      source,
    ]
  }
}

copy("node_assets") {
  sources = [
    "node_modules/highlight.js/styles/tomorrow-night.css",
    "node_modules/mermaid/dist/mermaid.min.js",
  ]
  deps = [ ":node_modules" ]

  outputs = [ "${perfetto_website_out_dir}/assets/{{source_file_part}}" ]
}

# WARNING: this does globbing at generation time. Incremental builds are not
# going to work properly if files are added/removed. `gn gen` needs to be
# rerun.
assets = exec_script("../../gn/standalone/glob.py",
                     [
                       "--root=" + rebase_path("src/assets", root_build_dir),
                       "--filter=*.png",
                       "--filter=*.js",
                     ],
                     "list lines")

src_assets = []

foreach(i, assets) {
  src_assets += [ rebase_path(i, ".", root_build_dir) ]
}

copy("assets") {
  sources = src_assets
  outputs = [ "${perfetto_website_out_dir}/assets/{{source_file_part}}" ]
}
