Skip to content

Commit

Permalink
codemod: prefix MIT Rust libraries with "sapling-"
Browse files Browse the repository at this point in the history
Summary:
Prefix with `sapling-` so we can publish the crates without worrying much about
conflicting. `features`, `dependencies` in `Cargo.toml` are updated too.
`streampager` is prefixed too so it does not conflict with upstream.

This is done by the script:

```python
import ast, copy, glob, json, os, re, sys
from collections import defaultdict
from functools import partial

class Ignored:
    def __getattr__(self, _name):
        return Ignored()

    def __call__(self, *args, **kwargs):
        return Ignored()

    def __add__(self, _rhs):
        return Ignored()

    def __getitem__(self, _name):
        return Ignored()

def parse_targets(code, path):
    """extract rust_library infos from TARGET files"""
    result = []

    def rust_library(**kwargs):
        line_no = sys._getframe(1).f_lineno
        result.append(
            {
                "autocargo": kwargs.get("autocargo") or {},
                "name": kwargs.get("crate") or kwargs.get("name"),
                "line": line_no,
            }
        )

    locals = defaultdict(Ignored)
    locals.update(
        {
            "rust_library": rust_library,
            "rust_python_library": rust_library,
            "hg_binary": rust_library,
            "glob": (lambda *a, **k: []),
            "get_oss_suffix": (lambda: ""),
        }
    )
    try:
        exec(code, globals(), locals)
    except Exception:
        print(f'When processing {path}')
        raise
    return result

def rewrite_arg(code, line_no, arg_name, arg_value):
    """rewrite an argument from a function call"""
    lines = code.splitlines(True)
    # assuming `code` is formatted, find "{arg_name} = " range
    for start_line_no in range(line_no, len(lines)):
        if f"{arg_name} = " in lines[start_line_no]:
            break
    else:
        lines.insert(line_no + 1, f"{arg_name} = {repr(arg_value)},\n")
        new_code = "".join(lines)
        return new_code
    for end_line_no in range(start_line_no + 1, len(lines)):
        test_code = "f(\n" + "".join(lines[start_line_no:end_line_no]) + "\n)"
        try:
            ast.parse(test_code)
        except Exception:
            continue
        new_code = (
            "".join(lines[:start_line_no])
            + f"{arg_name} = {repr(arg_value)},\n"
            + "".join(lines[end_line_no:])
        )
        return new_code
    else:
        raise RuntimeError("did not find the matching end-line")

def prefix_name_autocargo(autocargo, name):
    if "cargo_toml_config" not in autocargo:
        autocargo["cargo_toml_config"] = {}
    for key in ["package", "lib"]:
        if key not in autocargo["cargo_toml_config"]:
            autocargo["cargo_toml_config"][key] = {}
    autocargo["cargo_toml_config"]["package"]["name"] = "sapling-" + name
    autocargo["cargo_toml_config"]["lib"]["name"] = name

def _rewrite_dep(override, renamed):
    for key in ("dependencies", "dev-dependencies"):
        if key in override:
            old_value = override[key]
            new_value = {renamed.get(k) or k: v for k, v in old_value.items()}
            override[key] = new_value

def update_dep_overrides_autocargo(autocargo, renamed):
    orig_autocargo = copy.deepcopy(autocargo)
    override = (autocargo.get("cargo_toml_config") or {}).get("dependencies_override") or {}
    _rewrite_dep(override, renamed)
    target = override.get("target")
    if target is not None:
        for key, value in target.items():
            _rewrite_dep(value, renamed)

    # features
    def rewrite_feature(name, all_feature_names):
        if "/" in name:
            left, right = name.split("/", 1)
            return "%s/%s" % (renamed.get(left) or left, right)
        elif name.startswith("dep:"):
            rest = name.split(":", 1)[1]
            return "dep:%s" % (renamed.get(rest) or rest,)
        elif name in all_feature_names:
            return name
        else:
            return renamed.get(name) or name

    features = (autocargo.get("cargo_toml_config") or {}).get("features") or {}
    all_feature_names = set(features)
    for name, values in features.items():
        new_values = [rewrite_feature(v, all_feature_names) for v in values]
        features[name] = new_values
    changed = orig_autocargo != autocargo
    return changed

def main():
    renamed = {}
    # First pass - rename
    for path in glob.glob("scm/lib/**/TARGETS", recursive=True):
        if "third" in path and "streampager" not in path:
            continue
        orig_code = code = open(path, "rb").read().decode()
        license_path = os.path.join(os.path.dirname(path), "LICENSE")
        is_mit = "/lib/streampager/" in path
        if not is_mit and os.path.exists(license_path):
            is_mit = "MIT" in open(license_path).read()
        if not is_mit:
            continue
        parsed = parse_targets(code, path)
        for item in reversed(parsed):
            renamed[item["name"]] = "sapling-" + item["name"]
            prefix_name_autocargo(item["autocargo"], item["name"])
            code = rewrite_arg(code, item["line"], "autocargo", item["autocargo"])
        if orig_code != code:
            print(path)
            open(path, "wb").write(code.encode())
    # Second pass - update dep overrides
    for path in list(glob.glob("**/TARGETS", recursive=True)):
        if "third" in path or path in {"scm/tests/TARGETS"} or "hg-server" in path:
            continue
        orig_code = code = open(path, "rb").read().decode()
        parsed = parse_targets(code, path)
        # print(parsed)

        any_changed = False
        for item in reversed(parsed):
            changed = update_dep_overrides_autocargo(item["autocargo"], renamed)
            if changed:
                any_changed = True
                code = rewrite_arg(code, item["line"], "autocargo", item["autocargo"])
        if any_changed:
            # print(path)
            open(path, "wb").write(code.encode())

if __name__ == "__main__":
    main()
```

Reviewed By: lmvasquezg

Differential Revision: D65716841

fbshipit-source-id: 0865864c9a888e262aae75b3a41afe7e0a61ae9a
  • Loading branch information
quark-zju authored and facebook-github-bot committed Nov 10, 2024
1 parent ae80493 commit b9a8852
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 2 deletions.
2 changes: 1 addition & 1 deletion shed/sorted_vector_map/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ rust_library(
"name": "set",
},
],
"dependencies_override": {"dev-dependencies": {"minibench": {"version": None}}},
"dependencies_override": {"dev-dependencies": {"sapling-minibench": {"version": None}}},
"extra_buck_dependencies": {
"dev-dependencies": [
"//eden/scm/lib/minibench:minibench",
Expand Down
2 changes: 1 addition & 1 deletion shed/sorted_vector_map/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ itertools = "0.13.0"
quickcheck = "1.0"

[dev-dependencies]
minibench = { git = "https://github.com/facebook/sapling.git", branch = "main" }
sapling-minibench = { git = "https://github.com/facebook/sapling.git", branch = "main" }

0 comments on commit b9a8852

Please sign in to comment.