Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ bazel_dep(name = "aspect_rules_rollup", version = "2.0.1")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "rules_pkg", version = "1.1.0")
bazel_dep(name = "aspect_rules_ts", version = "3.6.0")
bazel_dep(name = "platforms", version = "0.0.9")
bazel_dep(name = "platforms", version = "0.0.11")

rules_ts_ext = use_extension(
"@aspect_rules_ts//ts:extensions.bzl",
Expand Down Expand Up @@ -64,4 +64,6 @@ bazel_binaries = use_extension("@rules_bazel_integration_test//:extensions.bzl",
bazel_binaries.download(version_file = ".bazelversion")
use_repo(bazel_binaries, "bazel_binaries", "bazel_binaries_bazelisk", "build_bazel_bazel_.bazelversion")
bazel_dep(name = "zlib", version = "1.3.1")
bazel_dep(name = "bazel_bats", version = "0.35.0")
bazel_dep(name = "bazel_bats", version = "0.35.0")

bazel_dep(name = "rules_python", version = "1.6.1")
15 changes: 11 additions & 4 deletions MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions python/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("@cgrindel_bazel_starlib//bzlformat:defs.bzl", "bzlformat_missing_pkgs", "bzlformat_pkg")

bzlformat_pkg(name = "bzlformat")

bzlformat_missing_pkgs(name = "bzlformat_missing_pkgs")

exports_files(["defs.bzl"])

bzl_library(
name = "defs",
srcs = ["defs.bzl"],
visibility = ["//visibility:public"],
deps = [
"//python/private:py_pipeline",
],
)
7 changes: 7 additions & 0 deletions python/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Public API for shell Bazel rules.
"""

load("//python/private:py_pipeline.bzl", _py_pipeline = "py_pipeline")

py_pipeline = _py_pipeline
43 changes: 43 additions & 0 deletions python/private/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
load("@aspect_rules_js//js:defs.bzl", "js_binary")
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("@cgrindel_bazel_starlib//bzlformat:defs.bzl", "bzlformat_missing_pkgs", "bzlformat_pkg")

bzlformat_pkg(name = "bzlformat")

bzlformat_missing_pkgs(name = "bzlformat_missing_pkgs")

filegroup(
name = "all_files",
srcs = glob(["*"]),
visibility = ["//:__subpackages__"],
)

js_binary(
name = "build_requirements",
entry_point = "build_requirements.mjs",
visibility = ["//visibility:public"],
)

exports_files(
glob(["*.bzl"]),
visibility = ["//docs:__pkg__"],
)

exports_files(
[
"pytest_wrapper.py.tmpl",
"pytest_wrapper.py",
],
visibility = ["//visibility:public"],
)

bzl_library(
name = "py_pipeline",
srcs = ["py_pipeline.bzl"],
visibility = [
"//docs:__subpackages__",
"//python:__subpackages__",
],
deps = [
],
)
61 changes: 61 additions & 0 deletions python/private/build_requirements.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Populate all the required fields for a package.json file needed for a full build
"""

load("@aspect_bazel_lib//lib:stamping.bzl", "STAMP_ATTRS", "maybe_stamp")

def _build_requirements_impl(ctx):
""" Builds a requirements.txt from the base and specified deps"""
output_file = ctx.actions.declare_file("requires.txt")
stamp = maybe_stamp(ctx)

stamp_inputs = []

args = {
"input": ctx.file.root_requirements.short_path,
"output": output_file.short_path,
"package_names": ctx.attr.package_names,
"substitutions": ctx.attr.substitutions,
}

if stamp:
stamp_inputs = [stamp.volatile_status_file, stamp.stable_status_file]
args["BAZEL_STABLE_STATUS_FILE"] = stamp.stable_status_file.path
args["BAZEL_VOLATILE_STATUS_FILE"] = stamp.volatile_status_file.path

ctx.actions.run(
inputs = stamp_inputs + ctx.files.root_requirements,
env = {
"BAZEL_BINDIR": ctx.bin_dir.path,
},
arguments = [json.encode(args)],
outputs = [output_file],
executable = ctx.executable._build_requirements,
)

return [
DefaultInfo(
files = depset([output_file]),
),
]

build_requirements = rule(
implementation = _build_requirements_impl,
attrs = dict({
"package_names": attr.string_list(
doc = "A list of packages to parse out of the requirements.txt file.",
mandatory = True,
),
"root_requirements": attr.label(
mandatory = True,
doc = "The root requirements.txt for the project. Used to get the versions of dependencies",
allow_single_file = ["requirements.txt"],
),
"substitutions": attr.string_dict(default = {}),
"_build_requirements": attr.label(
executable = True,
cfg = "exec",
default = "//python/private:build_requirements",
),
}, **STAMP_ATTRS),
)
136 changes: 136 additions & 0 deletions python/private/build_requirements.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import path from "node:path";
import fs from "node:fs";

const REQUIREMENTSREGEX =
/^([a-zA-Z0-9._-]+)(?:([<>=!~]+)([^,\s]+(?:,[<>=!~]+[^,\s]+)*))?\s*(?:#.*)?$/g;


/** Adds support for replacing process.env.* references with stamped values from bazel */
function getStampedSubstitutions(
stableStatusFile,
volatileStatusFile
) {
const substitutions = {};

[stableStatusFile, volatileStatusFile].forEach((statusFile) => {
if (!fs.existsSync(statusFile)) {
return;
}

const contents = fs.readFileSync(statusFile, "utf-8");

contents.split("\n").forEach((statusLine) => {
if (!statusLine.trim()) {
return;
}

const firstSpace = statusLine.indexOf(" ");
const varName = statusLine.substring(0, firstSpace);
const varVal = statusLine.substring(firstSpace + 1);
substitutions[varName] = varVal;
});
});

return substitutions;
}
function getStampVars({
substitutions,
BAZEL_STABLE_STATUS_FILE,
BAZEL_VOLATILE_STATUS_FILE,
}) {

if(!BAZEL_STABLE_STATUS_FILE || !BAZEL_VOLATILE_STATUS_FILE){
return {}
}

const stableStatusFile = path.join(
process.env.JS_BINARY__EXECROOT,
BAZEL_STABLE_STATUS_FILE
);
const volatileStatusFile = path.join(
process.env.JS_BINARY__EXECROOT,
BAZEL_VOLATILE_STATUS_FILE
);

if (!fs.existsSync(stableStatusFile) || !fs.existsSync(volatileStatusFile)) {
return {}
}

const customSubstitutions = getStampedSubstitutions(
substitutions,
stableStatusFile,
volatileStatusFile
);

return customSubstitutions;
}

async function main(args) {
const { JS_BINARY__EXECROOT } = process.env;
const {
input,
output,
package_names,
BAZEL_STABLE_STATUS_FILE,
BAZEL_VOLATILE_STATUS_FILE
} = args;

// Get stamp vars
const stampVars = getStampVars({
BAZEL_STABLE_STATUS_FILE,
BAZEL_VOLATILE_STATUS_FILE,
});

const local_version = stampVars['STABLE_VERSION'] ?? "0.0.0"

// clean up canary names to follow pep
local_version.replace("--",".").replace("-",".")

// Build map of requirement name to version
const requirementsFile = await fs.promises.readFile(
path.join(JS_BINARY__EXECROOT, input),
"utf-8",
);
const requirementsMap = new Map();

requirementsFile.split("\n").map((requirement) => {
const result = Array.from(requirement.matchAll(REQUIREMENTSREGEX), (m) =>
m.slice(1),
)[0];
if (result) {
const [pkg, range, version] = result;
requirementsMap.set(pkg, `${pkg}${range}${version}`);
}
});

// map requirements to version
const parsedRequirements = [];

//clean up requirement names that are passed in like @@rules_python++pip+pypi//pytest:pkg
const cleanedPackages = package_names.map((p) => {
if(p.startsWith("//")){
const localPackageName = p.split(":")[1].replace("_library", "")
// if its a local package we can handle it directly
requirementsMap.set(localPackageName, `${localPackageName}==${local_version}`);
return localPackageName
} else {
return p.split("//")[1].split(":")[0]
}
});


cleanedPackages.forEach((pkg) => {
if (requirementsMap.has(pkg)) {
parsedRequirements.push(`${requirementsMap.get(pkg)}`);
}
});

// write results
await fs.promises.mkdir(path.dirname(output), { recursive: true });
await fs.promises.writeFile(output, parsedRequirements.join("\n"));
}

main(JSON.parse(process.argv[2])).catch((e) => {
console.error(e);
process.exit(1);
});
Loading