Skip to content

Commit

Permalink
[antlir2][toolchain] track which projects actually exist
Browse files Browse the repository at this point in the history
Summary:
There are a ton of third-party deps used throughout fbsource. We cannot
possibly correctly define all of them (and keep it maintained) such that `buck2
uquery` works on any arbitrary `cxx_` rule, so instead let's track which
dependencies antlir2 actually knows about, so that the platform machinery in
the next diff never redirects to a non-existent target. This will keep `uquery`
clean, while only failing builds.

Test Plan:
```
❯ buck2 test fbcode//antlir/distro/deps:projects-unittest
Buck UI: https://www.internalfb.com/buck2/27eb0c66-0120-47e7-b810-4fc11adc02c7
Test UI: https://www.internalfb.com/intern/testinfra/testrun/16888498666849008
Tests finished: Pass 1. Fail 0. Fatal 0. Skip 0. Build failure 0
```

Reviewed By: naveedgol

Differential Revision: D68121683

fbshipit-source-id: 2c24e11b082d71634f7e7dbf0521d284d503661d
  • Loading branch information
vmagro authored and facebook-github-bot committed Jan 17, 2025
1 parent 5de5d13 commit 2f24111
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 1 deletion.
22 changes: 22 additions & 0 deletions antlir/distro/deps/BUCK
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# @oss-disable
load("//antlir/antlir2/bzl/feature:defs.bzl", "feature")
load("//antlir/antlir2/bzl/image:defs.bzl", "image")
load("//antlir/antlir2/bzl/package:defs.bzl", "package")
load("//antlir/antlir2/image_command_alias:image_command_alias.bzl", "image_command_alias")
load("//antlir/bzl:build_defs.bzl", "rust_binary")
load(":unavailable.bzl", "unavailable")

oncall("antlir")

Expand Down Expand Up @@ -64,3 +67,22 @@ image_command_alias(
rootless = True,
visibility = ["PUBLIC"],
)

rust_binary(
name = "projects",
srcs = ["projects.rs"],
# @oss-disable
test_srcs = ["projects.bzl"],
deps = [
"anyhow",
"serde",
"serde_json",
"serde_starlark",
"//antlir/signedsource:signedsource",
],
)

# Generic fallthrough library when antlir doesn't know about a dependency
unavailable(
name = "unknown",
)
21 changes: 21 additions & 0 deletions antlir/distro/deps/projects.bxl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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.

def _query_impl(ctx: bxl.Context):
libs = ctx.uquery().kind("alias|cxx_.*", "antlir//antlir/distro/deps/...")
deps = []
for target in libs:
if target.label.package == "antlir/distro/deps":
# only consider deps inside a sub-project
continue
project = target.label.package.removeprefix("antlir/distro/deps/")
name = target.label.name
deps.append(struct(project = project, name = name))
ctx.output.print_json(deps)

query = bxl_main(
impl = _query_impl,
cli_args = {},
)
14 changes: 14 additions & 0 deletions antlir/distro/deps/projects.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# @generated SignedSource<<2e84df60629fb37ec96b032917e29d5b>>
PROJECTS = [
("glibc", "c"),
("glibc", "dl"),
("glibc", "m"),
("glibc", "pthread"),
("jsoncpp", "jsoncpp"),
("libgcc", "atomic"),
("libgcc", "gcc_s"),
("libgcc", "stdc++"),
("libgcc", "stdc++-legacy"),
("openmp", "headers"),
("rpm", "librpm"),
]
98 changes: 98 additions & 0 deletions antlir/distro/deps/projects.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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.
*/

use std::path::Path;
use std::path::PathBuf;
use std::process::Command;

use anyhow::ensure;
use anyhow::Context;
use anyhow::Result;
use serde::ser::SerializeTuple;
use serde::ser::Serializer;
use serde::Deserialize;
use serde::Serialize;
use signedsource::sign_with_generated_header;
use signedsource::Comment;

#[derive(Debug, Deserialize)]
#[serde(rename = "struct")]
struct Dep {
project: String,
name: String,
}

impl Serialize for Dep {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut t = serializer.serialize_tuple(2)?;
t.serialize_element(&self.project)?;
t.serialize_element(&self.name)?;
t.end()
}
}

fn output_path() -> Result<PathBuf> {
let out = Command::new("buck2")
.arg("root")
.output()
.context("while running buck2 root")?;
ensure!(out.status.success(), "buck2 root failed");
let stdout = String::from_utf8(out.stdout).context("buck2 root output not utf8")?;
let root = Path::new(stdout.trim());
Ok(root.join("antlir/distro/deps/projects.bzl"))
}

fn gen_bzl(deps: &[Dep]) -> String {
let mut src = String::new();
src.push_str("PROJECTS = ");
src.push_str(&serde_starlark::to_string(deps).expect("failed to serialize"));
sign_with_generated_header(Comment::Starlark, &src)
}

fn get_deps() -> Result<Vec<Dep>> {
let out = Command::new("buck2")
.arg("bxl")
.arg("--reuse-current-config")
.arg("antlir//antlir/distro/deps/projects.bxl:query")
.output()
.context("while running bxl")?;
ensure!(
out.status.success(),
"bxl failed: {}",
String::from_utf8_lossy(&out.stderr)
);
serde_json::from_slice(&out.stdout).context("while deserializing deps")
}

fn main() -> Result<()> {
let out_path = output_path().context("while determining output path")?;
// before trying to get the dependencies, replace the bzl file with some
// known-good contents (empty) so that this binary can be ergonomically used
// to resolve merge conflicts
std::fs::write(&out_path, gen_bzl(&[])).context("while writing empty bzl")?;

let deps = get_deps().context("while getting deps")?;
let src = gen_bzl(&deps);
std::fs::write(&out_path, src)
.with_context(|| format!("while writing bzl at {}", out_path.display()))?;
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_is_in_sync() {
let deps = get_deps().expect("failed to get deps");
let src = gen_bzl(&deps);
assert_eq!(src, include_str!("projects.bzl"),);
}
}
36 changes: 36 additions & 0 deletions antlir/distro/deps/unavailable.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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.

# @oss-disable

def _unavailable_impl(ctx):
fail("""
This target is unavailable in the antlir system toolchain. It must be
defined in antlir/distro/deps in order to be usable.
If you're seeing this after running 'buck2 build $target', try `buck2 cquery 'somepath($target, {})'`
""".format(ctx.label.raw_target()))

_unavailable = rule(
impl = _unavailable_impl,
attrs = {
"labels": attrs.list(attrs.string(), default = []),
},
)

def unavailable(name: str):
"""
To have a working unconfigured buck2 target graph, we need to declare some
libraries as stubs that are not expected to actually be usable but exist to
keep the unconfigured graph happy.
"""
_unavailable(
name = name,
labels = [
# By definition, this thing won't build, so don't ever let CI try
# @oss-disable
],
visibility = ["PUBLIC"],
)
2 changes: 1 addition & 1 deletion ci/test_target_graph.bxl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# LICENSE file in the root directory of this source tree.

def _impl(ctx):
targets = ctx.cquery().eval("//...")
targets = ctx.cquery().eval("set(//...) - kind(_unavailable, //...)")

ctx.output.print("All BUCK files evaluate")

Expand Down

0 comments on commit 2f24111

Please sign in to comment.