mirror of
https://github.com/facebook/sapling.git
synced 2024-10-04 13:57:13 +03:00
11f0412245
Summary:
Although D39042765 (a04fc2e9b3
) appeared to successfully produce `.deb` files, it turns
out that they would only work if you already had the requisite package
dependencies installed. For example, running `hg --version` on a clean
Ubuntu instance would yield:
```
hg: error while loading shared libraries: libpython3.8.so.1.0: cannot open shared object file: No such file or directory
```
It turned out that our `DEBIAN/control` file was missing a key `Depends:` line
to tell `dpkg` what dependencies to install. Once again, I decided to look and
see how wezterm deals with this, and the solution appears to be `dpkg-shlibdeps`:
https://github.com/wez/wezterm/blob/97eaa58112a4/ci/deploy.sh#L234,L240
`dpkg-shlibdeps` looks at your executables and, based on the symbols, determines
what the appropriate packages are. For our Ubuntu 22.04 `.deb`, this turns out to be:
```
Depends: libc6 (>= 2.34), libgcc-s1 (>= 4.2), libpython3.10 (>= 3.10.0), libssl3 (>= 3.0.0~~alpha1), zlib1g (>= 1:1.1.4)
```
Apparently you cannot have a package in the `Depends:` line that comes from a PPA,
so if you want to make your `.deb` easy for users to install, that means you should
limit your dependencies to "standard" packages for the distro. In our case, that meant
that our Ubuntu 22.04 `.deb` should use Python 3.10 instead of Python 3.8 (which we
previously fetched from `ppa:deadsnakes/ppa`).
In order to support this, this diff makes a number of changes:
- Adds logic to `setup.py` to check the `PY_VERSION` environment variable to decide
whether to use Python 3.10 or something else.
- Updates `gen_workflows.py` to define different Python deps based on the Ubuntu version.
- Updates `gen_workflows.py` to remove the logic that uses the `ppa:deadsnakes/ppa`.
- Ran `buck2 run //eden/oss/ci:gen_workflows -- eden/oss/.github/workflows/` to update the GitHub Actions
- Moved the logic for the `deb` target in the `Makefile` into a separate shell script because
it was getting too complex to express directly in Make.
- Introduced separate `deb-ubuntu-20.04` and `deb-ubuntu-22.04` targets in the `Makefile`.
- Updated `bytearrayobject.rs` to support Python 3.10 in addition to Python 3.9.
- Updated `pick_python.py` to consider `python3.10` if `python3.8` is not found before going
down the rest of the list.
As you can see in the Test Plan, we are *really close* to things "just working," but we also have
to register `git` as a dependency, which is not discovered by `dpkg-shlibdeps`. This will be
addressed in D39156794.
Reviewed By: DurhamG
Differential Revision: D39156794
fbshipit-source-id: ca1e0a73096e0de97230804a97f316114b8bfc2e
308 lines
11 KiB
Python
Executable File
308 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
import math
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import yaml
|
|
|
|
DOCKERFILE_DIR = ".github/workflows"
|
|
|
|
CARGO_FETCH_PATHS = [
|
|
"eden/scm/exec/hgmain/Cargo.toml",
|
|
"eden/scm/edenscmnative/conch_parser/Cargo.toml",
|
|
"eden/scm/exec/scratch/Cargo.toml",
|
|
"eden/scm/exec/scm_daemon/Cargo.toml",
|
|
]
|
|
|
|
UBUNTU_VERSIONS = ["20.04", "22.04"]
|
|
|
|
UBUNTU_VERSION_DEPS = {
|
|
"20.04": [
|
|
"python3.8",
|
|
"python3.8-dev",
|
|
"python3.8-distutils",
|
|
],
|
|
"22.04": [
|
|
"python3.10",
|
|
"python3.10-dev",
|
|
"python3.10-distutils",
|
|
],
|
|
}
|
|
|
|
UBUNTU_DEPS = [
|
|
"nodejs",
|
|
"pkg-config",
|
|
"libssl-dev",
|
|
"cython3",
|
|
"make",
|
|
"g++",
|
|
"cargo",
|
|
# This is needed for dpkg-name.
|
|
"dpkg-dev",
|
|
]
|
|
|
|
|
|
def main() -> int:
|
|
"""Takes sys.argv[1] and uses it as the folder where all of the GitHub
|
|
actions should be written.
|
|
"""
|
|
out_dir = get_out_dir()
|
|
if out_dir is None:
|
|
eprint("must specify an output folder")
|
|
sys.exit(1)
|
|
|
|
workflows = WorkflowGenerator(out_dir)
|
|
for ubuntu_version in UBUNTU_VERSIONS:
|
|
workflows.gen_build_ubuntu_image(ubuntu_version=ubuntu_version)
|
|
workflows.gen_ubuntu_ci(ubuntu_version=ubuntu_version)
|
|
workflows.gen_ubuntu_release(ubuntu_version=ubuntu_version)
|
|
return 0
|
|
|
|
|
|
def get_out_dir() -> Optional[Path]:
|
|
try:
|
|
out_dir = sys.argv[1]
|
|
except IndexError:
|
|
return None
|
|
return Path(out_dir)
|
|
|
|
|
|
class WorkflowGenerator:
|
|
def __init__(self, out_dir: Path):
|
|
self.out_dir = out_dir
|
|
|
|
def gen_build_ubuntu_image(self, *, ubuntu_version: str) -> None:
|
|
image_name = f"ubuntu:{ubuntu_version}"
|
|
dockerfile_name = f"sapling-cli-ubuntu-{ubuntu_version}.Dockerfile"
|
|
cargo_prefetch_commands = "".join(
|
|
[f"RUN cargo fetch --manifest-path {p}\n" for p in CARGO_FETCH_PATHS]
|
|
)
|
|
if cargo_prefetch_commands:
|
|
cargo_prefetch_commands = (
|
|
"\n# Run `cargo fetch` on the crates we plan to build.\n"
|
|
+ cargo_prefetch_commands
|
|
)
|
|
|
|
full_deps = UBUNTU_DEPS + UBUNTU_VERSION_DEPS[ubuntu_version]
|
|
dockerfile = f"""\
|
|
FROM {image_name}
|
|
|
|
# https://serverfault.com/a/1016972 to ensure installing tzdata does not
|
|
# result in a prompt that hangs forever.
|
|
ARG DEBIAN_FRONTEND=noninteractive
|
|
ENV TZ=Etc/UTC
|
|
|
|
# Update and install some basic packages to register a PPA.
|
|
RUN apt-get -y update
|
|
RUN apt-get -y install curl git
|
|
|
|
# Use a PPA to ensure a specific version of Node (the default Node on
|
|
# Ubuntu 20.04 is v10, which is too old):
|
|
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
|
|
|
|
# Now we can install the bulk of the packages:
|
|
RUN apt-get -y install {' '.join(full_deps)}
|
|
|
|
# Copy the full repo over because `cargo fetch` follows deps within the repo,
|
|
# so assume it needs everything.
|
|
COPY . /tmp/repo
|
|
WORKDIR /tmp/repo
|
|
{cargo_prefetch_commands}
|
|
# Create and populate a Yarn offline mirror by running `yarn install`
|
|
# in the addons/ folder that contains yarn.lock, package.json, and the
|
|
# package.json file for each entry in the Yarn workspace.
|
|
RUN npm install --global yarn
|
|
RUN yarn config set yarn-offline-mirror "$HOME/npm-packages-offline-cache"
|
|
RUN [ -f /tmp/repo/addons ] && yarn --cwd /tmp/repo/addons install || true
|
|
|
|
# Verify the yarn-offline-mirror was populated.
|
|
RUN find $(yarn config get yarn-offline-mirror)
|
|
|
|
# Clean up to reduce the size of the Docker image.
|
|
WORKDIR /root
|
|
RUN rm -rf /tmp/repo
|
|
"""
|
|
out_file = self.out_dir / dockerfile_name
|
|
with out_file.open("w") as f:
|
|
f.write(dockerfile)
|
|
|
|
container = self._get_ubuntu_container_name(ubuntu_version)
|
|
gh_action_build_image = {
|
|
"name": f"Docker Image - {image_name}",
|
|
"on": "workflow_dispatch",
|
|
"jobs": {
|
|
"clone-and-build": {
|
|
"runs-on": "ubuntu-latest",
|
|
"steps": [
|
|
{"name": "Checkout Code", "uses": "actions/checkout@v3"},
|
|
{
|
|
"name": "Set up Docker Buildx",
|
|
"uses": "docker/setup-buildx-action@v2",
|
|
},
|
|
{
|
|
"name": "Login to GitHub Container Registry",
|
|
"uses": "docker/login-action@v2",
|
|
"with": {
|
|
"registry": "ghcr.io",
|
|
"username": "${{ github.repository_owner }}",
|
|
"password": "${{ secrets.GITHUB_TOKEN }}",
|
|
},
|
|
},
|
|
{
|
|
"name": "Build and Push Docker Image",
|
|
"uses": "docker/build-push-action@v3",
|
|
"with": {
|
|
"context": ".",
|
|
"file": f"{DOCKERFILE_DIR}/{dockerfile_name}",
|
|
"push": True,
|
|
"tags": container,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
self._write_file(
|
|
f"sapling-cli-ubuntu-{ubuntu_version}-image.yml", gh_action_build_image
|
|
)
|
|
|
|
def gen_ubuntu_ci(self, *, ubuntu_version: str) -> str:
|
|
gh_action = {
|
|
"name": f"CI - Ubuntu {ubuntu_version}",
|
|
"on": "workflow_dispatch",
|
|
"jobs": {
|
|
"build-deb": self.gen_build_ubuntu_cli_job(
|
|
ubuntu_version=ubuntu_version, deb_version_expr="v0"
|
|
)
|
|
},
|
|
}
|
|
self._write_file(f"sapling-cli-ubuntu-{ubuntu_version}-ci.yml", gh_action)
|
|
|
|
def gen_ubuntu_release(self, *, ubuntu_version: str) -> str:
|
|
"""Logic for releases is modeled after wezterm's GitHub Actions:
|
|
https://github.com/wez/wezterm/tree/6962d6805abf/.github/workflows
|
|
|
|
Note that the build job is run in a Docker image, but the upload is
|
|
done on the host, as empirically, that is more reliable.
|
|
"""
|
|
|
|
BUILD = "build"
|
|
artifact_key = f"ubuntu-{ubuntu_version}"
|
|
build_job = self.gen_build_ubuntu_cli_job(
|
|
ubuntu_version=ubuntu_version, deb_version_expr="${{ github.ref }}"
|
|
)
|
|
build_job["steps"].append(
|
|
{
|
|
"name": "Upload Artifact",
|
|
"uses": "actions/upload-artifact@v3",
|
|
"with": {"name": artifact_key, "path": "./eden/scm/sapling_*.deb"},
|
|
}
|
|
)
|
|
|
|
publish_job = {
|
|
"runs-on": "ubuntu-latest",
|
|
"needs": BUILD,
|
|
"steps": [
|
|
{"name": "Checkout Code", "uses": "actions/checkout@v3"},
|
|
grant_repo_access(),
|
|
{
|
|
"name": "Download Artifact",
|
|
"uses": "actions/download-artifact@v3",
|
|
"with": {"name": artifact_key},
|
|
},
|
|
{
|
|
"name": "Create pre-release",
|
|
"env": {"GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}"},
|
|
"shell": "bash",
|
|
"run": "bash ci/retry.sh bash ci/create-release.sh $(ci/tag-name.sh)",
|
|
},
|
|
{
|
|
"name": "Upload Release",
|
|
"env": {"GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}"},
|
|
"shell": "bash",
|
|
"run": "bash ci/retry.sh gh release upload --clobber $(ci/tag-name.sh) sapling_*.deb",
|
|
},
|
|
],
|
|
}
|
|
gh_action = {
|
|
"name": f"Release - Ubuntu {ubuntu_version}",
|
|
"on": {"push": {"tags": ["v*", "test-release-*"]}},
|
|
"jobs": {
|
|
BUILD: build_job,
|
|
"publish": publish_job,
|
|
},
|
|
}
|
|
self._write_file(f"sapling-cli-ubuntu-{ubuntu_version}-release.yml", gh_action)
|
|
|
|
def gen_build_ubuntu_cli_job(self, *, ubuntu_version: str, deb_version_expr: str):
|
|
container = self._get_ubuntu_container_name(ubuntu_version)
|
|
# https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
|
|
# documents the constraints on this segment of the version number.
|
|
DEB_UPSTREAM_VERISION = "DEB_UPSTREAM_VERISION"
|
|
return {
|
|
"runs-on": "ubuntu-latest",
|
|
"container": {"image": container},
|
|
"steps": [
|
|
{"name": "Checkout Code", "uses": "actions/checkout@v3"},
|
|
grant_repo_access(),
|
|
create_set_env_step(
|
|
DEB_UPSTREAM_VERISION, "$(ci/tag-name.sh | tr \\- .)"
|
|
),
|
|
{
|
|
"name": "Create .deb",
|
|
"working-directory": "./eden/scm",
|
|
"run": f"${{{{ format('VERSION=0.0-{{0}} make deb-ubuntu-{ubuntu_version}', env.{DEB_UPSTREAM_VERISION}) }}}}",
|
|
},
|
|
{
|
|
"name": "Rename .deb",
|
|
"working-directory": "./eden/scm",
|
|
"run": f"${{{{ format('mv sapling_0.0-{{0}}_amd64.deb sapling_0.0-{{0}}_amd64.Ubuntu{ubuntu_version}.deb', env.{DEB_UPSTREAM_VERISION}, env.{DEB_UPSTREAM_VERISION}) }}}}",
|
|
},
|
|
],
|
|
}
|
|
|
|
def _get_ubuntu_container_name(self, version: str) -> str:
|
|
"""Name of container to use when doing builds on Ubuntu: will be built
|
|
by "Docker Image" GitHub Action.
|
|
"""
|
|
name = f"build_ubuntu_{version.replace('.', '_')}"
|
|
return gen_container_name(name=name)
|
|
|
|
def _write_file(self, filename: str, gh_action):
|
|
path = self.out_dir / filename
|
|
with path.open("w") as f:
|
|
yaml.dump(gh_action, f, width=math.inf, sort_keys=False)
|
|
|
|
|
|
def gen_container_name(*, name: str, tag: Optional[str] = "latest") -> str:
|
|
return f"${{{{ format('ghcr.io/{{0}}/{name}:{tag}', github.repository) }}}}"
|
|
|
|
|
|
def create_set_env_step(env_var: str, env_expr):
|
|
"""See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable"""
|
|
return {
|
|
"name": f"set-env {env_var}",
|
|
"run": f'echo "{env_var}={env_expr}" >> $GITHUB_ENV',
|
|
}
|
|
|
|
|
|
def grant_repo_access():
|
|
return {
|
|
"name": "Grant Access",
|
|
"run": 'git config --global --add safe.directory "$PWD"',
|
|
}
|
|
|
|
|
|
def eprint(*args, **kwargs):
|
|
print(*args, file=sys.stderr, **kwargs)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|