diff --git a/.github/workflows/generate_bundles.yml b/.github/workflows/generate_bundles.yml new file mode 100644 index 0000000..bb2df75 --- /dev/null +++ b/.github/workflows/generate_bundles.yml @@ -0,0 +1,36 @@ +name: Generate + Publish Bundles + +on: + push: + pull_request: + +jobs: + generate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python generate_year_bundles.py -o bundles 2024 2025 + - uses: actions/upload-artifact@v4 + with: + name: bundles + path: bundles + retention-days: 1 + + publish: + if: github.repository == 'wpilibsuite/vendor-json-repo' && github.ref == 'refs/heads/main' + needs: [generate] + runs-on: ubuntu-latest + steps: + - uses: jfrog/setup-jfrog-cli@v4 + with: + disable-auto-build-publish: true + env: + JF_ENV_1: ${{ secrets.ARTIFACTORY_CLI_SECRET }} + - uses: actions/download-artifact@v4 + with: + name: bundles + path: bundles + - run: jf rt upload "bundles/(*)" "vendordeps/vendordep-marketplace/{1}" --sync-deletes "vendordeps/vendordep-marketplace/" --quiet diff --git a/.gitignore b/.gitignore index 994cc30..fab5ffb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *.sw? bazel-* -user.bazelrc \ No newline at end of file +user.bazelrc + +bundles diff --git a/2024_metadata.json b/2024_metadata.json new file mode 100644 index 0000000..332ca9a --- /dev/null +++ b/2024_metadata.json @@ -0,0 +1,62 @@ +[ + { + "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", + "name": "CTRE-Phoenix (v6)", + "description": "CTR-Electronics Phoenix 6 framework", + "website": "https://v6.docs.ctr-electronics.com/en/latest/index.html" + }, + { + "uuid": "ab676553-b602-441f-a38d-f1296eff6537", + "name": "CTRE-Phoenix (v5)", + "description": "CTR-Electronics Phoenix 5 framework", + "website": "https://v5.docs.ctr-electronics.com/en/latest/index.html" + }, + { + "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", + "name": "REVLib", + "description": "REV Robotics REVLib C++/Java", + "website": "https://docs.revrobotics.com/brushless/spark-max/revlib" + }, + { + "uuid": "287cff6e-1b60-4412-8059-f6834fb30e30", + "name": "ChoreoLib", + "description": "A graphical tool for planning time-optimized trajectories for autonomous mobile robots in the FIRST Robotics Competition.", + "website": "https://github.com/SleipnirGroup/Choreo" + }, + { + "uuid": "cb311d09-36e9-4143-a032-55bb2b94443b", + "name": "NavX", + "description": "navX2-MXP includes software which makes navX2-MXP easier to understand, integrate and use with FIRST FRC and FTC robots", + "website": "https://pdocs.kauailabs.com/navx-mxp/software/roborio-libraries/" + }, + { + "uuid": "1b42324f-17c6-4875-8e77-1c312bc8c786", + "name": "PathplannerLib", + "description": "PathPlanner is a motion profile generator for FRC robots created by team 3015.", + "website": "https://pathplanner.dev/home.html" + }, + { + "uuid": "515fe07e-bfc6-11fa-b3de-0242ac130004", + "name": "PhotonLib", + "description": "PhotonVision is the free, fast, and easy-to-use vision processing solution for the FIRST Robotics Competition.", + "website": "https://docs.photonvision.org/en/latest/docs/programming/photonlib/adding-vendordep.html" + }, + { + "uuid": "14b8ad04-24df-11ea-978f-2e728ce88125", + "name": "playingwithfusion", + "description": "This library supports the Venom motor/controller as well as the CAN enabled Time of Flight sensor.", + "website": "https://www.playingwithfusion.com/docview.php?docid=1205" + }, + { + "uuid": "151ecca8-670b-4026-8160-cdd2679ef2bd", + "name": "ReduxLib", + "description": "To make performant and open products at affordable costs to teams.", + "website": "https://docs.reduxrobotics.com/reduxlib" + }, + { + "uuid": "1ccce5a4-acd2-4d18-bca3-4b8047188400", + "name": "yagsl", + "description": "YAGSL is a Swerve Library Developed by current and former BroncBotz mentors for all FRC Teams.", + "website": "https://yagsl.gitbook.io/yagsl" + } +] \ No newline at end of file diff --git a/2025_metadata.json b/2025_metadata.json new file mode 100644 index 0000000..822b56c --- /dev/null +++ b/2025_metadata.json @@ -0,0 +1,68 @@ +[ + { + "uuid": "cb311d09-36e9-4143-a032-55bb2b94443b", + "name": "Studica", + "description": "Libraries for NavX-MXP and NavX-Micro", + "website": "https://pdocs.kauailabs.com/navx-mxp/software/roborio-libraries/" + }, + { + "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", + "name": "CTRE-Phoenix (v6)", + "description": "Libraries for Phoenix 6 devices", + "website": "https://docs.ctr-electronics.com/" + }, + { + "uuid": "e7900d8d-826f-4dca-a1ff-182f658e98af", + "name": "CTRE-Phoenix Replay (v6)", + "description": "Libraries for Phoenix 6 devices with Hoot Replay", + "website": "https://docs.ctr-electronics.com/" + }, + { + "uuid": "ab676553-b602-441f-a38d-f1296eff6537", + "name": "CTRE-Phoenix (v5)", + "description": "Libraries for Phoenix 5 devices", + "website": "https://docs.ctr-electronics.com/" + }, + { + "uuid": "1b42324f-17c6-4875-8e77-1c312bc8c786", + "name": "PathplannerLib", + "description": "PathPlanner's powerful robot-side vendor library", + "website": "https://pathplanner.dev/pathplannerlib.html" + }, + { + "uuid": "65592ce1-2251-4a31-8e4b-2df20dacebe4", + "name": "DogLog", + "description": "Simpler logging for FRC", + "website": "https://doglog.dev/" + }, + { + "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", + "name": "REVLib", + "description": "Library for all REV devices including SPARK Flex, SPARK MAX, and Color Sensor V3", + "website": "https://docs.revrobotics.com/brushless/revlib/revlib-overview" + }, + { + "uuid": "84246d17-a797-4d1e-bd9f-c59cd8d2477c", + "name": "URCL", + "description": "Unofficial REV-Compatible Logger for AdvantageScope", + "website": "https://docs.advantagescope.org/more-features/urcl" + }, + { + "uuid": "151ecca8-670b-4026-8160-cdd2679ef2bd", + "name": "ReduxLib", + "description": "Redux Robotics 2025 Beta", + "website": "https://docs.reduxrobotics.com/reduxlib" + }, + { + "uuid": "c39481e8-4a63-4a4c-9df6-48d91e4da37b", + "name": "maplesim", + "description": "FRC Java Robot Simulation using a physics engine", + "website": "https://github.com/Shenzhen-Robotics-Alliance/maple-sim" + }, + { + "uuid": "1ccce5a4-acd2-4d18-bca3-4b8047188400", + "name": "Yet Another Generic Swerve Library (YAGSL)", + "description": "YAGSL (Yet Another Generic Swerve Library) is an open-source robotics library designed to simplify and optimize the control, simulation, and configuration of swerve drive systems of all types.", + "website": "https://yagsl.gitbook.io/yagsl" + } +] \ No newline at end of file diff --git a/generate_year_bundles.py b/generate_year_bundles.py new file mode 100644 index 0000000..353151a --- /dev/null +++ b/generate_year_bundles.py @@ -0,0 +1,101 @@ +import argparse +import json +import shutil +from pathlib import Path + + +def check_metadata_schema(metadata: list[dict]): + required_keys = {"uuid", "name", "website", "description"} + for entry in metadata: + # no nested types, so just check root keys + if not required_keys.issubset(entry.keys()): + raise KeyError( + f"Missing one or more required keys: {required_keys - entry.keys()}, metadata listing: {entry}" + ) + + +def load_metadata(file: Path) -> dict[str, dict]: + json_metadata = json.loads(file.read_text()) + check_metadata_schema(json_metadata) + out = {} + for entry in json_metadata: + out[entry["uuid"]] = entry + return out + + +def generate_entry( + file: Path, path_prefix: str, metadata_database: dict[str, dict] +) -> dict[str, str]: + vendordep_data = json.loads(file.read_text()) + if path_prefix and not path_prefix.endswith("/"): + path_prefix += "/" + uuid = vendordep_data["uuid"] + if uuid not in metadata_database.keys(): + raise KeyError(f"UUID for {file} not found in metadata.") + metadata = metadata_database[uuid] + # Metadata schemas have already been checked for required keys, so we can just add all the values to the output + # This allows optional keys to be added as necessary without changing generation + return metadata | { + "path": path_prefix + file.name, + "version": vendordep_data["version"], + } + + +def generate_manifest_file( + json_files: list[Path], metadata_file: Path, path_prefix: str, outfile: Path +): + """Generates a manifest for all vendordep json files in json_files.""" + metadata_database = load_metadata(metadata_file) + entries = [] + for file in json_files: + entries.append(generate_entry(file, path_prefix, metadata_database)) + outfile.write_text(json.dumps(entries, indent=2), newline="\n") + + +def generate_bundle(year: str, root: Path, outdir: Path): + """Generates a 'bundle' consisting of a YEAR.json manifest and a directory named YEAR containing all of the vendordep files + + Requires a metadata file YEAR_metadata.json, and a directory named YEAR containing the input vendordeps. + """ + json_dir = root / year + metadata = root / f"{year}_metadata.json" + path_prefix = year + outdir.mkdir(parents=True, exist_ok=True) + + manifest_file = Path(outdir) / f"{year}.json" + vendordeps = [file for file in json_dir.glob("*.json")] + + generate_manifest_file(vendordeps, metadata, path_prefix, manifest_file) + + # Copy all vendordeps to outdir/YEAR + depsdir = outdir / year + depsdir.mkdir(exist_ok=True) + for file in vendordeps: + shutil.copy(file, depsdir) + + +def main(): + parser = argparse.ArgumentParser( + "Generates one or more vendordep repository bundles for publication" + ) + parser.add_argument( + "--output", "-o", type=Path, help="Directory to place the output bundles in" + ) + parser.add_argument( + "--root", + "-r", + type=Path, + default=Path(), + help="Root directory to find metadata files and year folders. Defaults to '.'", + ) + parser.add_argument( + "year", nargs="+", type=str, help="Years to generate bundles for" + ) + args = parser.parse_args() + + for year in args.year: + generate_bundle(year, args.root, args.output) + + +if __name__ == "__main__": + main()