Skip to content

Commit

Permalink
feat: bzlmod CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
zaucy committed Sep 14, 2023
1 parent b096367 commit d6413e3
Show file tree
Hide file tree
Showing 16 changed files with 556 additions and 8 deletions.
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ bazel_dep(name = "libdeflate", version = "1.18")
bazel_dep(name = "abseil-cpp", version = "20230802.0")
bazel_dep(name = "boringssl", version = "0.0.0-20230215-5c22014")
bazel_dep(name = "docopt.cpp")

git_override(
module_name = "docopt.cpp",
commit = "e2f9cdba36c3b70883cea848a8e4d72d9b9a3fac",
Expand Down
56 changes: 56 additions & 0 deletions bzlmod/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")

cc_library(
name = "add_module",
hdrs = ["add_module.hh"],
srcs = ["add_module.cc"],
deps = [
":get_registries",
":find_workspace_dir",
":download_module_metadata",
"@boost.process",
],
)
cc_library(
name = "init_module",
hdrs = ["init_module.hh"],
srcs = ["init_module.cc"],
deps = [
"@abseil-cpp//absl/strings",
],
)
cc_library(
name = "download_module_metadata",
hdrs = ["download_module_metadata.hh"],
srcs = ["download_module_metadata.cc"],
deps = [
"//bzlreg:download",
"//bzlreg:config_types",
],
)

cc_library(
name = "get_registries",
hdrs = ["get_registries.hh"],
srcs = ["get_registries.cc"],
deps = [
"@abseil-cpp//absl/strings",
],
)
cc_library(
name = "find_workspace_dir",
hdrs = ["find_workspace_dir.hh"],
srcs = ["find_workspace_dir.cc"],
deps = [
],
)

cc_binary(
name = "bzlmod",
srcs = ["bzlmod.cc"],
deps = [
"@docopt.cpp//:docopt",
":add_module",
":init_module",
],
)
113 changes: 113 additions & 0 deletions bzlmod/add_module.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include "bzlmod/add_module.hh"

#include <filesystem>
#include <iostream>
#include <algorithm>
#include <execution>
#include <format>
#include <boost/process.hpp>
#include "bzlmod/get_registries.hh"
#include "bzlmod/find_workspace_dir.hh"
#include "bzlmod/download_module_metadata.hh"

namespace bp = boost::process;
namespace fs = std::filesystem;

struct registry_resolve_entry {
std::string_view registry;
std::string module_version;
};

auto bzlmod::add_module( //
std::string_view dep_name
) -> int {
auto buildozer = bp::search_path("buildozer");
if(buildozer.empty()) {
std::cerr << std::format(
"[ERROR] `buildozer` is required to use `bzlmod add`. Please make sure "
"it's in your PATH. Buildozer may be downloaded here:\n"
" https://github.com/bazelbuild/buildtools/releases\n\n"
);
return 1;
}

auto workspace_dir = find_workspace_dir(fs::current_path());

if(!workspace_dir) {
std::cerr << std::format(
"[ERROR] Cannot find bazel workspace from {}."
" Did you mean `bzlmod init`?\n",
fs::current_path().generic_string()
);
return 1;
}

auto registries = get_registries(*workspace_dir);

if(!registries) {
std::cerr << "[ERROR] Unable to read .bazelrc file(s)\n";
return 1;
}

auto registry_resolve_entries = std::vector<registry_resolve_entry>{};
registry_resolve_entries.reserve(registries->size());
for(auto& registry : *registries) {
registry_resolve_entries.emplace_back(registry, "");
}

std::for_each(
std::execution::par,
registry_resolve_entries.begin(),
registry_resolve_entries.end(),
[&](registry_resolve_entry& entry) {
auto metadata_url = std::format( //
"{}/modules/{}/metadata.json",
entry.registry,
dep_name
);
auto metadata = bzlmod::download_module_metadata(metadata_url);

if(metadata && !metadata->versions.empty()) {
entry.module_version = metadata->versions.back();
}
}
);

auto dep_version = std::optional<std::string>{};

for(auto& entry : registry_resolve_entries) {
if(!entry.module_version.empty()) {
dep_version = entry.module_version;
break;
}
}

if(!dep_version) {
std::cerr << "Failed to find " << dep_name << " in:\n";
for(auto& entry : registry_resolve_entries) {
std::cerr << "\t" << entry.registry << "\n";
}
return 1;
}

std::cout << "Found version for " << dep_name << ": " << *dep_version << "\n";

bp::child{
bp::exe(buildozer),
bp::args({
std::format("new bazel_dep {}", dep_name),
"//MODULE.bazel:all",
}),
}.wait();


bp::child{
bp::exe(buildozer),
bp::args({
std::format("set version {}", *dep_version),
std::format("//MODULE.bazel:{}", dep_name),
}),
}.wait();

return 0;
}
9 changes: 9 additions & 0 deletions bzlmod/add_module.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <string_view>

namespace bzlmod {
auto add_module( //
std::string_view dep_name
) -> int;
}
38 changes: 38 additions & 0 deletions bzlmod/bzlmod.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <filesystem>
#include <iostream>
#include "docopt.h"
#include "bzlmod/init_module.hh"
#include "bzlmod/add_module.hh"

namespace fs = std::filesystem;

constexpr auto USAGE = R"docopt(
Bzlmod - manage your bazel module with _ease_
Usage:
bzlmod init [<module-dir>]
bzlmod add <dep-name>
)docopt";

auto main(int argc, char* argv[]) -> int {
auto bazel_working_dir = std::getenv("BUILD_WORKING_DIRECTORY");
if(bazel_working_dir != nullptr) {
fs::current_path(bazel_working_dir);
}

auto args = docopt::docopt(USAGE, {argv + 1, argv + argc});
auto exit_code = int{0};

if(args["init"].asBool()) {
auto module_dir = args["<module-dir>"] //
? fs::path{args.at("<module-dir>").asString()}
: fs::current_path();

exit_code = bzlmod::init_module(module_dir);
} else if(args["add"].asBool()) {
auto dep_name = args["<dep-name>"].asString();
exit_code = bzlmod::add_module(dep_name);
}

return exit_code;
}
18 changes: 18 additions & 0 deletions bzlmod/download_module_metadata.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include "bzlmod/download_module_metadata.hh"

#include "nlohmann/json.hpp"
#include "bzlreg/download.hh"

using nlohmann::json;

auto bzlmod::download_module_metadata( //
std::string_view url
) -> std::optional<bzlreg::metadata_config> {
auto data = bzlreg::download_file(url);
if(!data) {
return std::nullopt;
}

bzlreg::metadata_config metadata = json::parse(*data);
return metadata;
}
11 changes: 11 additions & 0 deletions bzlmod/download_module_metadata.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <string_view>
#include <optional>
#include "bzlreg/config_types.hh"

namespace bzlmod {
auto download_module_metadata( //
std::string_view url
) -> std::optional<bzlreg::metadata_config>;
}
19 changes: 19 additions & 0 deletions bzlmod/find_workspace_dir.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "bzlmod/find_workspace_dir.hh"

#include <filesystem>

namespace fs = std::filesystem;

auto bzlmod::find_workspace_dir( //
std::filesystem::path start_dir
) -> std::optional<std::filesystem::path> {
if(fs::exists(start_dir / "MODULE.bazel")) {
return start_dir;
}

if(start_dir.has_parent_path()) {
return find_workspace_dir(start_dir.parent_path());
}

return std::nullopt;
}
10 changes: 10 additions & 0 deletions bzlmod/find_workspace_dir.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include <filesystem>
#include <optional>

namespace bzlmod {
auto find_workspace_dir(
std::filesystem::path start_dir
) -> std::optional<std::filesystem::path>;
}
Loading

0 comments on commit d6413e3

Please sign in to comment.