Source code

Revision control

Copy as Markdown

Other Tools

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import hashlib
import os
import subprocess
import tempfile
import buildconfig
from mozbuild.nodeutil import find_node_executable
from mozbuild.util import FileAvoidWrite
HASHED_EXTENSIONS = (".jsx", ".js", ".mjs", ".scss")
def _newtab_dir():
return os.path.join(buildconfig.topsrcdir, "browser", "extensions", "newtab")
def _node():
node, _ = find_node_executable(nodejs_exe=buildconfig.substs.get("NODEJS"))
if not node:
raise Exception(
"Node.js is required to build newtab bundles. "
"Ensure node is in PATH or set NODEJS in your environment."
)
return node
def _node_modules():
node_modules = os.path.join(
buildconfig.topsrcdir, "third_party", "node", "node_modules"
)
if not os.path.isdir(node_modules):
raise Exception(
"The vendored node packages under third_party/node are missing. "
"Run `./mach vendor node`, or check out third_party/node."
)
return node_modules
def _deps_record(objdir):
return os.path.join(objdir, "webpack-deps.txt")
def _reported_inputs(objdir):
"""Files webpack read during the previous build. The FasterMake backend
ignores the dependency files an action returns, so hashing these is what
makes a change to one of them rebuild the bundles."""
record = _deps_record(objdir)
if not os.path.exists(record):
return set()
with open(record, encoding="utf-8") as f:
return {line.strip() for line in f if line.strip()}
def _declared_inputs(newtab_dir):
inputs = {
os.path.join(newtab_dir, "package.json"),
os.path.join(newtab_dir, "webpack.system-addon.config.js"),
os.path.join(buildconfig.topsrcdir, "browser", "tools", "mozsrcUriPlugin.js"),
os.path.join(buildconfig.topsrcdir, "browser", "tools", "resourceUriPlugin.js"),
os.path.join(buildconfig.topsrcdir, "browser", "modules", "Dedupe.sys.mjs"),
os.path.join(
buildconfig.topsrcdir, "third_party", "node", "vendor-inputs.hash"
),
}
for directory in (
os.path.join(newtab_dir, "content-src"),
os.path.join(newtab_dir, "common"),
os.path.join(buildconfig.topsrcdir, "browser", "components", "topsites"),
):
for root, dirs, files in os.walk(directory):
dirs.sort()
inputs.update(
os.path.join(root, f) for f in files if f.endswith(HASHED_EXTENSIONS)
)
return inputs
def hash_sources(output):
objdir = os.path.dirname(output.name)
inputs = _declared_inputs(_newtab_dir()) | _reported_inputs(objdir)
hasher = hashlib.sha256()
for path in sorted(inputs):
if not os.path.isfile(path):
continue
hasher.update(path.encode("utf-8"))
with open(path, "rb") as f:
hasher.update(f.read())
output.write(hasher.digest())
def _record_reported_inputs(objdir, deps_file):
if not os.path.exists(deps_file):
return
with open(deps_file, encoding="utf-8") as f:
reported = sorted({line.strip() for line in f if line.strip()})
kept = [path for path in reported if os.path.isfile(path)]
with FileAvoidWrite(_deps_record(objdir)) as record:
for path in kept:
record.write(path + "\n")
def _run(argv, newtab_dir, node_modules, extra_env=None):
env = dict(os.environ)
env["MOZ_NODE_MODULES"] = node_modules
env["WEBPACK_CLI_SKIP_IMPORT_LOCAL"] = "1"
env.update(extra_env or {})
result = subprocess.run(argv, check=False, cwd=newtab_dir, env=env)
if result.returncode != 0:
raise Exception(f"{argv[1]} failed with exit code {result.returncode}")
def generate_js(output, sources_stamp):
newtab_dir = _newtab_dir()
node_modules = _node_modules()
webpack = os.path.join(node_modules, "webpack", "bin", "webpack.js")
content_dir = os.path.dirname(output.name)
as_path = os.path.join(content_dir, "activity-stream.bundle.js")
objdir = os.path.dirname(os.path.dirname(content_dir))
with tempfile.TemporaryDirectory() as tmpdir:
deps_file = os.path.join(tmpdir, "webpack-deps.txt")
_run(
[
_node(),
webpack,
"--config",
os.path.join(newtab_dir, "webpack.system-addon.config.js"),
"--env",
"outputPath=" + tmpdir,
],
newtab_dir,
node_modules,
extra_env={"MOZ_WEBPACK_DEPS": deps_file},
)
with open(os.path.join(tmpdir, "vendor.bundle.js"), "rb") as f:
output.write(f.read())
with FileAvoidWrite(as_path, readmode="rb") as as_output:
with open(os.path.join(tmpdir, "activity-stream.bundle.js"), "rb") as f:
as_output.write(f.read())
_record_reported_inputs(objdir, deps_file)
def generate_css(output, sources_stamp):
newtab_dir = _newtab_dir()
node_modules = _node_modules()
sass = os.path.join(node_modules, "sass", "sass.js")
css_dir = os.path.dirname(output.name)
nova_css_path = os.path.join(css_dir, "nova", "activity-stream.css")
src_styles_dir = os.path.join(newtab_dir, "content-src", "styles")
with tempfile.TemporaryDirectory() as tmpdir:
_run(
[
_node(),
sass,
src_styles_dir + ":" + tmpdir,
"--no-source-map",
],
newtab_dir,
node_modules,
)
with open(os.path.join(tmpdir, "activity-stream.css"), "rb") as f:
output.write(f.read())
os.makedirs(os.path.dirname(nova_css_path), exist_ok=True)
with FileAvoidWrite(nova_css_path, readmode="rb") as nova_output:
with open(os.path.join(tmpdir, "nova", "activity-stream.css"), "rb") as f:
nova_output.write(f.read())