blob: 5696b43d40ceee1a158205f1b98961edf28ac35e [file] [log] [blame]
# Copyright 2025 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import base64
import shutil
import subprocess
import sys
import urllib.request
from pathlib import Path
import logger
LOCAL_JIRI_MANIFEST_CONTENT = """
<manifest>
<imports>
<localimport file="manifests/third_party/all"/>
<localimport file="manifests/prebuilts"/>
</imports>
</manifest>
"""
class Prebuilts:
"""A class to manage prebuilts for a cog workspace."""
def __init__(
self,
cartfs_directory: str,
workspace_dir: str,
workspace_name: str,
repo_name: str,
):
self.cartfs_directory = Path(cartfs_directory)
self.workspace_dir = Path(workspace_dir)
self.workspace_name = workspace_name
self.repo_name = repo_name
def create_symlink(self, target: Path, link_name: Path) -> None:
"""Creates a symlink from link_name to target.
If a symlink already exists at link_name and points to target, this
function does nothing.
If a file, directory, or a different symlink exists at link_name, it will
be removed and replaced with the new symlink.
"""
if link_name.is_symlink() and link_name.readlink() == target:
return
# If the path exists but is not the desired symlink, remove it.
if link_name.is_dir() and not link_name.is_symlink():
shutil.rmtree(link_name)
else:
link_name.unlink(missing_ok=True)
link_name.symlink_to(target)
def _run_bootstrap_jiri_script(self) -> None:
"""Runs the bootstrap jiri script."""
url = "https://fuchsia.googlesource.com/jiri/+/HEAD/scripts/bootstrap_jiri?format=TEXT"
try:
with urllib.request.urlopen(url) as response:
encoded_script = response.read()
decoded_script = base64.b64decode(encoded_script)
subprocess.run(
["bash", "-s", self.cartfs_directory],
input=decoded_script,
check=True,
)
except (urllib.error.URLError, subprocess.CalledProcessError) as e:
logger.log_error(f"Failed to bootstrap jiri: {e}")
sys.exit(1)
def _write_jiri_manifest(self) -> None:
"""Writes the jiri manifest."""
self._patch_file(
filepath=".jiri_manifest",
content=LOCAL_JIRI_MANIFEST_CONTENT,
symlink=True,
)
def _write_jiri_config(self) -> None:
"""Initialize the jiri config."""
logger.log_info("Initialize the jiri config.")
subprocess.run(
[
".jiri_root/bin/jiri",
"init",
"-analytics-opt=true",
self.cartfs_directory,
],
cwd=self.cartfs_directory,
check=True,
)
def _create_jiri_snapshot(self) -> None:
"""Create snapshot."""
logger.log_info("Create snapshot at .jiri_root/update_history/latest.")
(self.cartfs_directory / ".jiri_root/update_history").mkdir(
parents=True, exist_ok=True
)
subprocess.run(
[
".jiri_root/bin/jiri",
"snapshot",
".jiri_root/update_history/latest",
],
cwd=self.cartfs_directory,
check=True,
)
def is_jiri_bootstrapped(self) -> bool:
"""Checks if jiri is bootstrapped."""
jiri_root = self.cartfs_directory / ".jiri_root"
jiri_manifest = self.cartfs_directory / ".jiri_manifest"
return jiri_root.is_dir() and jiri_manifest.exists()
def bootstrap_jiri(self) -> None:
"""Bootstraps jiri if it is not already bootstrapped."""
logger.log_info("Bootstrapping jiri.")
self._run_bootstrap_jiri_script()
self._write_jiri_manifest()
def fetch_prebuilts(self) -> None:
"""Fetches prebuilts for the given repo."""
logger.log_info(f"Fetching prebuilts for {self.repo_name}.")
subprocess.run(
[".jiri_root/bin/jiri", "fetch-packages"],
cwd=self.cartfs_directory,
check=True,
)
def cartfs_structure_initialization(self) -> None:
"""Create essential artifacts used by build."""
# Create files
self._patch_file(filepath="integration/MILESTONE", content="30")
self._patch_file(
filepath="build/cipd.gni",
content="internal_access = false",
symlink=True,
)
self._patch_file(
filepath="build/info/jiri_generated/integration_commit_hash.txt",
content="20560e50d0a87e8c0093b7ed21ebcaa46e64bb50",
symlink=True,
)
self._patch_file(
filepath="build/info/jiri_generated/integration_commit_stamp.txt",
content="1762987703",
symlink=True,
)
self._patch_file(
filepath="build/info/jiri_generated/integration_daily_commit_hash.txt",
content="843090d610fd85d7c7ffc4d1adf3abd01d367ae8",
symlink=True,
)
self._patch_file(
filepath="build/info/jiri_generated/integration_daily_commit_stamp.txt",
content="1762905105",
symlink=True,
)
# Copy manifests directory to CartFS.
logger.log_info("Copy manifests directory to CartFS.")
shutil.copytree(
self.workspace_dir / self.repo_name / "manifests",
self.cartfs_directory / "manifests",
dirs_exist_ok=True,
)
self._write_jiri_config()
self._create_jiri_snapshot()
# Create directories
(self.cartfs_directory / ".fx").mkdir(exist_ok=True)
# Initialize git repository in the submodules
submodules = [
"third_party/mesa-migrating/src",
"third_party/boringssl/src",
"third_party/glslang/src",
"third_party/go",
]
for submodule in submodules:
# This would create a .git/HEAD
submodule_path = self.workspace_dir / self.repo_name / submodule
if (submodule_path / ".git").exists():
continue
subprocess.run(
["git", "init", "-b", "main"],
cwd=submodule_path,
check=True,
)
# This would create a .git/index
subprocess.run(
["git", "reset"],
cwd=submodule_path,
check=True,
)
def create_symlinks(self) -> None:
"""Creates symlinks for the prebuilts."""
logger.log_info("Creating symlinks for the prebuilts.")
# Link the paths in the repo to cartfs
for path in [
"prebuilt",
".jiri_root",
".cipd",
".fx",
"integration",
]:
repo_path = self.workspace_dir / self.repo_name / path
cartfs_path = self.cartfs_directory / path
logger.log_info(
f"Creating symlink from {repo_path} to {cartfs_path}"
)
self.create_symlink(
cartfs_path,
repo_path,
)
# Link .jiri_root/bin/{fx, ffx, hermetic-env, fuchsia-vendored-python}
# LINT.IfChange
self.create_symlink(
self.workspace_dir / self.repo_name / "scripts/fx",
self.cartfs_directory / ".jiri_root/bin/fx",
)
self.create_symlink(
self.workspace_dir
/ self.repo_name
/ "src/developer/ffx/scripts/ffx",
self.cartfs_directory / ".jiri_root/bin/ffx",
)
self.create_symlink(
self.workspace_dir / self.repo_name / "scripts/hermetic-env",
self.cartfs_directory / ".jiri_root/bin/hermetic-env",
)
self.create_symlink(
self.workspace_dir
/ self.repo_name
/ "scripts/fuchsia-vendored-python",
self.cartfs_directory / ".jiri_root/bin/fuchsia-vendored-python",
)
# LINT.ThenChange(//scripts/devshell/lib/add_symlink_to_bin.sh)
# Symlink in cog workspace specific GN arg overrides.
(self.workspace_dir / self.repo_name / "local").mkdir(exist_ok=True)
self.create_symlink(
self.workspace_dir
/ self.repo_name
/ "scripts/cog/resources/args.gn",
self.workspace_dir / self.repo_name / "local/args.gn",
)
def _patch_file(
self, filepath: str, content: str, symlink: bool = False
) -> None:
"""Patches the file in cartFS."""
logger.log_info(f"Patching the {filepath} file.")
full_filepath = self.cartfs_directory / filepath
if not full_filepath.exists():
logger.log_info(
f"File {full_filepath} does not exist. Creating it now."
)
full_filepath.parent.mkdir(parents=True, exist_ok=True)
try:
full_filepath.write_text(content)
except Exception as e:
logger.log_error(
f"An error occurred while writing the file: {e}"
)
else:
logger.log_info(f"File {full_filepath} already exists.")
# Symlink from workspace if workspace path is specified
if symlink:
self.create_symlink(
full_filepath,
self.workspace_dir / self.repo_name / filepath,
)