diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..dc69238d --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,55 @@ +--- +name: Deploy odoc to GitHub Pages + +on: + push: + branches: + - main + +permissions: read-all + +concurrency: + group: deploy-odoc + cancel-in-progress: true + +jobs: + deploy-odoc: + name: Deploy odoc to GitHub Pages + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + permissions: + contents: read + id-token: write + pages: write + + runs-on: ubuntu-latest + + steps: + - name: Checkout tree + uses: actions/checkout@v4 + + - name: Set-up OCaml + uses: ocaml/setup-ocaml@v3 + with: + ocaml-compiler: 5 + + - name: Install dependencies + run: opam install . --deps-only --with-doc + + - name: Build documentation + run: opam exec -- dune build @doc + + - name: Set-up Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: _build/default/_doc/_html + + - name: Deploy odoc to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 213937c5..f4380b0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,17 @@ -*.install -.merlin -_build -_opam -var *.cap +*.install +*.log .*.swp +.DS_Store +.env +.idea +.merlin .vscode +_build/ +_opam/ capnp-secrets -.env +tmp/ +var + +# Particular to opam-ci-check +/.opam-revdeps/ diff --git a/Dockerfile b/Dockerfile index ed53601a..c4a9791d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,11 @@ FROM ocaml/opam:debian-12-ocaml-4.14 AS build RUN sudo ln -f /usr/bin/opam-2.1 /usr/bin/opam && opam init --reinit -ni RUN sudo apt-get update && sudo apt-get install libev-dev capnproto graphviz m4 pkg-config libsqlite3-dev libgmp-dev libffi-dev -y --no-install-recommends RUN cd ~/opam-repository && git fetch origin master && git reset --hard 8df59b79937426fefdf443f2202c514b5dfa479d && opam update -COPY --chown=opam opam-repo-ci-service.opam opam-repo-ci-api.opam /src/ +COPY --chown=opam opam-repo-ci-service.opam opam-repo-ci-api.opam opam-ci-check.opam /src/ WORKDIR /src RUN opam install -y --deps-only . ADD --chown=opam . . -RUN opam exec -- dune build ./_build/install/default/bin/opam-repo-ci-service -RUN cp $(opam exec -- which opam-ci-check) /src/_build/install/default/bin/opam-ci-check +RUN opam exec -- dune build ./_build/install/default/bin/opam-repo-ci-service ./_build/install/default/bin/opam-ci-check FROM debian:12 RUN apt-get update && apt-get install libev4 openssh-client curl gnupg2 dumb-init git graphviz libsqlite3-dev ca-certificates netbase gzip bzip2 xz-utils unzip tar docker.io -y --no-install-recommends diff --git a/Dockerfile.web b/Dockerfile.web index 965d06ba..dc4f4464 100644 --- a/Dockerfile.web +++ b/Dockerfile.web @@ -2,7 +2,7 @@ FROM ocaml/opam:debian-12-ocaml-4.14 AS build RUN sudo ln -f /usr/bin/opam-2.1 /usr/bin/opam && opam init --reinit -ni RUN sudo apt-get update && sudo apt-get install libev-dev capnproto m4 pkg-config libgmp-dev libffi-dev -y --no-install-recommends RUN cd ~/opam-repository && git fetch origin master && git reset --hard 8df59b79937426fefdf443f2202c514b5dfa479d && opam update -COPY --chown=opam opam-repo-ci-api.opam opam-repo-ci-web.opam opam-repo-ci-service.opam /src/ +COPY --chown=opam opam-repo-ci-api.opam opam-repo-ci-web.opam opam-repo-ci-service.opam opam-ci-check.opam /src/ WORKDIR /src RUN opam install -y --deps-only . ADD --chown=opam . . diff --git a/doc/dune b/doc/dune index fcba3520..0b54f97d 100644 --- a/doc/dune +++ b/doc/dune @@ -1,13 +1,17 @@ (executable (name doc) + (public_name opam_repo_ci_service_doc) + (package opam-repo-ci-service) (libraries opam_repo_ci)) (rule + (package opam-repo-ci-service) (target platforms-new.md) (action - (run ./doc.exe -o %{target}))) + (run %{dep:./doc.exe} -o %{target}))) (rule + (package opam-repo-ci-service) (alias doc) (mode promote) (action diff --git a/dune-project b/dune-project index bd3e8da4..a9a7ebf9 100644 --- a/dune-project +++ b/dune-project @@ -1,3 +1,3 @@ -(lang dune 3.7) +(lang dune 3.16) (name opam-repo-ci) (formatting (enabled_for dune)) \ No newline at end of file diff --git a/lib/dune b/lib/dune index b164fec3..7429d8e5 100644 --- a/lib/dune +++ b/lib/dune @@ -1,5 +1,6 @@ (library (name opam_repo_ci) + (package opam-repo-ci-api) (preprocess (pps ppx_deriving.std ppx_deriving_yojson)) (libraries diff --git a/opam-ci-check.opam b/opam-ci-check.opam new file mode 100644 index 00000000..ae2c7534 --- /dev/null +++ b/opam-ci-check.opam @@ -0,0 +1,43 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: + "CLI tool that tests whether packages are publishable on the opam repository" +description: + "opam-ci-check is used in the opam repo CI and can be used locally and in other CI pipelines to test the deliverability of packages." +maintainer: [ + "Puneeth Chaganti " + "Shon Feder " +] +authors: [ + "Puneeth Chaganti " + "Shon Feder " +] +license: "Apache-2.0" +tags: ["opam" "ci" "lint" "reverse dependency"] +homepage: "https://github.com/ocurrent/opam-ci-check" +doc: "https://www.ocurrent.org/opam-ci-check/opam-ci-check/index.html" +bug-reports: "https://github.com/ocurrent/opam-ci-check/issues" +depends: [ + "ocaml" {>= "4.14.0"} + "dune" {>= "3.16"} + "sexplib" + "cmdliner" {>= "1.1.1"} + "opam-client" {>= "2.3.0~alpha1"} + "mula" {>= "0.1.2"} + "odoc" {with-doc} +] +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "git+https://github.com/ocurrent/opam-ci-check.git" diff --git a/opam-ci-check/.ocamlformat b/opam-ci-check/.ocamlformat new file mode 100644 index 00000000..0b457a32 --- /dev/null +++ b/opam-ci-check/.ocamlformat @@ -0,0 +1,2 @@ +profile = default +version = 0.26.2 diff --git a/opam-ci-check/README.md b/opam-ci-check/README.md new file mode 100644 index 00000000..7ca75503 --- /dev/null +++ b/opam-ci-check/README.md @@ -0,0 +1,31 @@ +# opam-ci-check + +## Synopsis + +A CLI tool that tests whether packages are publishable on the opam repository. + +This tool is in progress. + +## Installation + +``` sh +opam pin opam-ci-check https://github.com/ocurrent/opam-repo-ci.git +``` + +## Documentation + +https://www.ocurrent.org/opam-repo-ci + +## Contribution + +Contributions are most welcome! + + +- [File issues](https://github.com/ocurrent/opam-ci-check/issues) to report bugs or feature requests. +- [Contribute code or documentation](./CONTRIBUTING.md) + +--- + +This project is created and maintained by\ +Tarides + diff --git a/opam-ci-check/Tarides.svg b/opam-ci-check/Tarides.svg new file mode 100644 index 00000000..72e83965 --- /dev/null +++ b/opam-ci-check/Tarides.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/opam-ci-check/bin/dune b/opam-ci-check/bin/dune new file mode 100644 index 00000000..aa87551a --- /dev/null +++ b/opam-ci-check/bin/dune @@ -0,0 +1,5 @@ +(executable + (public_name opam-ci-check) + (name main) + (libraries opam_ci_check cmdliner opam-client) + (package opam-ci-check)) diff --git a/opam-ci-check/bin/main.ml b/opam-ci-check/bin/main.ml new file mode 100644 index 00000000..6643eef9 --- /dev/null +++ b/opam-ci-check/bin/main.ml @@ -0,0 +1,191 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +open Cmdliner +open Opam_ci_check + +(* This is Cmdliner.Term.map, which is not available in Cmdliner 1.1.1 *) +let map_term f x = Term.app (Term.const f) x + +let to_exit_code : (unit, string) result Term.t -> Cmd.Exit.code Term.t = + map_term @@ function + | Ok () -> 0 + | Error msg -> + Printf.eprintf "%s%!" msg; + 1 + +let lint (changed_pkgs, new_pkgs) local_repo_dir = + match local_repo_dir with + | None -> failwith "TODO: default to using the opam repository" + | Some d -> ( + print_endline @@ Printf.sprintf "Linting opam-repository at %s ..." d; + match Lint.check ~new_pkgs ~changed_pkgs d with + | [] -> + print_endline "No errors"; + Ok () + | errors -> + errors |> List.map Lint.msg_of_error |> String.concat "\n" + |> Printf.sprintf "%s\n" |> Result.error) + +let show_revdeps pkg local_repo_dir no_transitive_revdeps = + (* Get revdeps for the package *) + let revdeps = + Revdeps.list_revdeps ?opam_repo:local_repo_dir + ~transitive:(not no_transitive_revdeps) + pkg + in + Revdeps.Display.packages revdeps; + Ok () + +let testing_revdeps_confirmed revdeps = + OpamConsole.confirm "Do you want test %d revdeps?" (List.length revdeps) + +let test_revdeps pkg local_repo_dir use_dune no_transitive_revdeps = + (* Get revdeps for the package *) + let revdeps = + Revdeps.list_revdeps ?opam_repo:local_repo_dir + ~transitive:(not no_transitive_revdeps) + pkg + in + + (* Install and test the first reverse dependency *) + let latest_versions = Revdeps.find_latest_versions revdeps in + + Revdeps.Display.packages latest_versions; + if not (testing_revdeps_confirmed latest_versions) then Ok () + else + match (use_dune, local_repo_dir) with + | true, Some d -> Test.test_packages_with_dune d pkg latest_versions + | true, None -> Error "Opam local repository path must be specified!\n" + | false, _ -> + let num_failed_installs = + Test.test_packages_with_opam pkg latest_versions + |> Seq.map (fun e -> Printf.eprintf "%s\n" (Test.error_to_string e)) + |> Seq.length + in + if num_failed_installs = 0 then Ok () + else + Error + (Printf.sprintf "tests failed in %d reverse dependencies" + num_failed_installs) + +let make_abs_path s = + if Filename.is_relative s then Filename.concat (Sys.getcwd ()) s else s + +let opam_repo_dir = + let parse s = + if Sys.file_exists s then + let repo_file = Filename.concat s "repo" in + let packages_dir = Filename.concat s "packages" in + if Sys.file_exists repo_file && Sys.is_directory packages_dir then + Ok (Some (make_abs_path s)) + else + Error + (`Msg + "The specified directory does not look like an opam repository. It \ + doesn't contain required 'repo' file or 'packages' directory.") + else Error (`Msg "The specified directory does not exist.") + in + let print fmt = function Some s -> Format.fprintf fmt "%s" s | None -> () in + Arg.conv (parse, print) + +let local_opam_repo_term = + let info = + Arg.info [ "r"; "opam-repository" ] + ~doc: + "Path to local clone of Opam Repository. This is optional and only \ + required if we wish to test a version of a package not released on \ + the opam repository." + in + Arg.value (Arg.opt opam_repo_dir None info) + +let no_transitive_revdeps = + let info = + Arg.info [ "no-transitive" ] + ~doc: + "Don't test transitive reverse dependencies - only test the direct \ + reverse dependencies." + in + Arg.value (Arg.flag info) + +let use_dune_term = + let info = + Arg.info [ "d"; "use-dune" ] + ~doc:"Use dune to build, install and test the reverse dependencies." + in + Arg.value (Arg.flag info) + +let pkg_term = + let info = Arg.info [] ~doc:"Package name + version" in + Arg.required (Arg.pos 0 (Arg.some Arg.string) None info) + +let changed_pkgs_term = + let info = + Arg.info + [ "c"; "changed-packages" ] + ~doc:"List of changed package name + version" + in + Arg.value (Arg.opt (Arg.list Arg.string) [] info) + +let newly_published_pkgs_term = + let info = + Arg.info [ "n"; "newly-published" ] + ~doc:"List of newly published package name + version" + in + Arg.value (Arg.opt (Arg.list Arg.string) [] info) + +let packages_term = + let create_term changed_pkgs newly_published_pkgs = + if changed_pkgs = [] && newly_published_pkgs = [] then + `Error + ( false, + "You must provide at least one changed or newly published package." ) + else `Ok (changed_pkgs, newly_published_pkgs) + in + Term.(ret (const create_term $ changed_pkgs_term $ newly_published_pkgs_term)) + +let lint_cmd = + let doc = "Lint the opam repository directory" in + let term = + Term.(const lint $ packages_term $ local_opam_repo_term) |> to_exit_code + in + let info = + Cmd.info "lint" ~doc ~sdocs:"COMMON OPTIONS" ~exits:Cmd.Exit.defaults + in + Cmd.v info term + +let list_cmd = + let doc = "List the revdeps for a package" in + let term = + Term.( + const show_revdeps $ pkg_term $ local_opam_repo_term + $ no_transitive_revdeps) + |> to_exit_code + in + let info = + Cmd.info "list" ~doc ~sdocs:"COMMON OPTIONS" ~exits:Cmd.Exit.defaults + in + Cmd.v info term + +let test_cmd = + let doc = "Test the revdeps for a package" in + let term = + Term.( + const test_revdeps $ pkg_term $ local_opam_repo_term $ use_dune_term + $ no_transitive_revdeps) + |> to_exit_code + in + let info = + Cmd.info "test" ~doc ~sdocs:"COMMON OPTIONS" ~exits:Cmd.Exit.defaults + in + Cmd.v info term + +let cmd : Cmd.Exit.code Cmd.t = + let doc = "A tool to list revdeps and test the revdeps locally" in + let exits = Cmd.Exit.defaults in + let default = Term.(ret (const (fun _ -> `Help (`Pager, None)) $ const ())) in + let info = Cmd.info "opam-ci-check" ~doc ~sdocs:"COMMON OPTIONS" ~exits in + Cmd.group ~default info [ lint_cmd; list_cmd; test_cmd ] + +let () = exit (Cmd.eval' cmd) diff --git a/opam-ci-check/lib/dir_helpers.ml b/opam-ci-check/lib/dir_helpers.ml new file mode 100644 index 00000000..73b8f606 --- /dev/null +++ b/opam-ci-check/lib/dir_helpers.ml @@ -0,0 +1,38 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +(*Generate random temporary file names. **) +let temp_file_name dir prefix suffix = + let prng = Random.State.make_self_init () in + let random_suffix = string_of_int (Random.State.int prng 0x10000000) in + Filename.concat dir (prefix ^ random_suffix ^ suffix) + +(*Create a temporary directory with the given prefix. **) +let create_temp_dir prefix = + let base_temp_dir = Filename.get_temp_dir_name () in + let unique_temp_dir = temp_file_name base_temp_dir prefix "" in + Unix.mkdir unique_temp_dir 0o700; + unique_temp_dir + +let remove_dir dir = + let rec remove_dir_rec dir = + let entries = Sys.readdir dir in + Array.iter + (fun entry -> + let entry = Filename.concat dir entry in + let entry_is_directory = + (* Sys.is_directory raises a Sys_error for broken symlinks *) + try Sys.is_directory entry with Sys_error _ -> false + in + if entry_is_directory then remove_dir_rec entry else Sys.remove entry) + entries; + Sys.rmdir dir + in + remove_dir_rec dir + +let with_temp_dir prefix f = + let dir = create_temp_dir prefix in + let res = f dir in + remove_dir dir; + res diff --git a/opam-ci-check/lib/dune b/opam-ci-check/lib/dune new file mode 100644 index 00000000..7f3412d9 --- /dev/null +++ b/opam-ci-check/lib/dune @@ -0,0 +1,4 @@ +(library + (name opam_ci_check) + (public_name opam-ci-check) + (libraries opam-client sexplib mula str)) diff --git a/opam-ci-check/lib/dune_helpers.ml b/opam-ci-check/lib/dune_helpers.ml new file mode 100644 index 00000000..2619ddc1 --- /dev/null +++ b/opam-ci-check/lib/dune_helpers.ml @@ -0,0 +1,115 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +let write_file ~file ~contents = + let ch = open_out file in + output_string ch contents; + close_out_noerr ch + +let chdir dir ~f = + let old_dir = Sys.getcwd () in + (try + Sys.chdir dir; + f () + with e -> + Sys.chdir old_dir; + raise e); + Sys.chdir old_dir + +let generate_dune_project packages = + let open Sexplib in + let pkg_sexps = + List.map + (fun pkg -> + Sexp.( + List + [ + Atom (OpamPackage.name_to_string pkg); + List [ Atom "="; Atom (OpamPackage.version_to_string pkg) ]; + ])) + packages + in + let expressions = + Sexp. + [ + List [ Atom "lang"; Atom "dune"; Atom "3.15" ]; + List + [ + Atom "package"; + List [ Atom "name"; Atom "dummy" ]; + List ([ Atom "depends" ] @ pkg_sexps); + ]; + ] + in + expressions |> List.map Sexp.to_string |> String.concat "\n" + +let generate_dune_workspace opam_repository = + let open Sexplib in + let expressions = + Sexp. + [ + List [ Atom "lang"; Atom "dune"; Atom "3.15" ]; + List [ Atom "lock_dir"; List [ Atom "repositories"; Atom "local" ] ]; + List + [ + Atom "repository"; + List [ Atom "name"; Atom "local" ]; + List [ Atom "source"; Atom opam_repository ]; + ]; + ] + in + expressions |> List.map Sexp.to_string |> String.concat "\n" + +let create_dummy_project root opam_repository packages = + let dir_name = + packages |> List.map OpamPackage.to_string |> String.concat "-" + in + let dir = Printf.sprintf "%s/%s" root dir_name in + (* Create dune-project *) + let contents = generate_dune_project packages in + let _ = Sys.command (Printf.sprintf "mkdir -p %s" dir) in + chdir dir ~f:(fun () -> write_file ~file:"dune-project" ~contents); + (* Create dune-workspace *) + let contents = generate_dune_workspace opam_repository in + chdir dir ~f:(fun () -> write_file ~file:"dune-workspace" ~contents); + print_endline (Printf.sprintf "Generated dune project in %s/dune-project" dir); + dir + +let take n lst = + List.rev + (List.fold_left + (fun acc x -> if List.length acc < n then x :: acc else acc) + [] lst) + +let get_ocaml_version () = + let ic = Unix.open_process_in "ocamlc --version" in + let rec read_all acc = + try + let line = input_line ic in + read_all (acc ^ line ^ "\n") + with End_of_file -> acc + in + let output = read_all "" in + let exit_status = Unix.close_process_in ic in + match exit_status with + | Unix.WEXITED 0 -> output |> String.trim + | _ -> "5.1.1" + +let create_dummy_projects root opam_repository target packages = + let ocaml_version = get_ocaml_version () in + let ocaml_pkg = "ocaml-system." ^ ocaml_version in + List.map + (fun pkg -> + create_dummy_project root opam_repository + (* FIXME: Current OCaml version? Should be configurable *) + [ target; pkg; OpamPackage.of_string ocaml_pkg ]) + packages + +let generate_lock_and_build dir = + chdir dir ~f:(fun () -> + let _ = Sys.command "dune pkg lock --verbose" in + (* FIXME: Just build won't run the tests of the revdep package. We need + to run the tests of the rev-dep package, not the dummy project! *) + let _ = Sys.command "dune build --verbose" in + ()) diff --git a/opam-ci-check/lib/env.ml b/opam-ci-check/lib/env.ml new file mode 100644 index 00000000..55ac5a8b --- /dev/null +++ b/opam-ci-check/lib/env.ml @@ -0,0 +1,66 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +let make_repo path = + let repo_name = OpamRepositoryName.of_string "default" in + let repo_url = OpamUrl.parse path in + { OpamTypes.repo_name; repo_url; repo_trust = None } + +let local_opam_root () = + let switch_dir = ".opam-revdeps" in + OpamFilename.Dir.of_string switch_dir + +let create_local_switch_maybe repo_path = + let root_dir = local_opam_root () in + let create_switch_dir repo_path = + OpamClientConfig.opam_init ~root_dir (); + (* opam init*) + let init_config = OpamInitDefaults.init_config ~sandboxing:true () in + let shell = OpamStd.Sys.guess_shell_compat () in + let repo = make_repo repo_path in + let gt, rt, _ = + OpamClient.init ~init_config ~repo ~bypass_checks:true ~interactive:false + ~update_config:false shell + in + (* opam switch create *) + let update_config = true in + let switch = OpamSwitch.of_string "default" in + (* FIXME: OCaml version should be a CLI arg? *) + let compiler_pkg = OpamPackage.of_string "ocaml-base-compiler.5.1.1" in + let compiler_name = OpamPackage.name compiler_pkg in + let compiler_version = (`Eq, OpamPackage.version compiler_pkg) in + let version_constraint = OpamFormula.Atom compiler_version in + let invariant = OpamFormula.Atom (compiler_name, version_constraint) in + let _created, _st = + OpamSwitchCommand.create gt ~rt ~update_config ~invariant switch + (fun st -> + let compiler = (compiler_name, Some compiler_version) in + OpamCoreConfig.update ~confirm_level:`unsafe_yes (); + (true, OpamClient.install st [ compiler ])) + in + () + in + (* FIXME: reinit if already exists? *) + if not (OpamFilename.exists_dir root_dir) then + match repo_path with + | Some d -> + OpamConsole.msg + "Creating local opam switch in %s with default repository URL %s\n" + (OpamFilename.Dir.to_string root_dir) + d; + create_switch_dir d + | None -> failwith "Opam local repository path must be specified!" + else () + +let with_locked_switch () = + let root_dir = local_opam_root () in + OpamClientConfig.opam_init ~root_dir (); + OpamGlobalState.with_ `Lock_none @@ fun gt -> + OpamSwitchState.with_ `Lock_write gt + +let with_unlocked_switch () = + let root_dir = local_opam_root () in + OpamClientConfig.opam_init ~root_dir (); + OpamGlobalState.with_ `Lock_none @@ fun gt -> + OpamSwitchState.with_ `Lock_none gt diff --git a/opam-ci-check/lib/lint.ml b/opam-ci-check/lib/lint.ml new file mode 100644 index 00000000..b3808b43 --- /dev/null +++ b/opam-ci-check/lib/lint.ml @@ -0,0 +1,419 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +module D = Dir_helpers +module O = Opam_helpers + +let ( // ) = Filename.concat +let get_files dir = dir |> Sys.readdir |> Array.to_list + +include Lint_error + +module Checks = struct + module Prefix = struct + (* For context, see https://github.com/ocurrent/opam-repo-ci/pull/316#issuecomment-2160069803 *) + let prefix_conflict_class_map = + [ + ("mysys2-", "msys2-env"); + ("arch-", "ocaml-arch"); + ("ocaml-env-mingw", "ocaml-env-mingw"); + ("ocaml-env-msvc", "ocaml-env-msvc"); + ("host-arch-", "ocaml-host-arch"); + ("host-system-", "ocaml-host-system"); + ("system-", "ocaml-system"); + ] + + let conflict_class_prefix_map = + List.map (fun (a, b) -> (b, a)) prefix_conflict_class_map + + let prefixes = List.map fst prefix_conflict_class_map + + let check_name_restricted_prefix ~pkg _opam = + let name = OpamPackage.name_to_string pkg in + List.filter_map + (fun prefix -> + if String.starts_with ~prefix name then + Some (pkg, RestrictedPrefix prefix) + else None) + prefixes + + let check_prefix_without_conflict_class ~pkg name conflict_classes = + let prefix = + List.find_opt (fun prefix -> String.starts_with ~prefix name) prefixes + in + match prefix with + | None -> [] + | Some prefix -> ( + match List.assoc_opt prefix prefix_conflict_class_map with + | Some required_conflict_class -> + if List.mem required_conflict_class conflict_classes then [] + else + [ + ( pkg, + PrefixConflictClassMismatch + (WrongConflictClass { prefix; required_conflict_class }) + ); + ] + | None -> + (* NOTE: We should ideally never reach here. It would be a logic + error, if we do. *) + failwith + @@ Printf.sprintf + "BUG: prefix '%s' not found in conflict class map" prefix) + + let check_conflict_class_without_prefix ~pkg name conflict_classes = + List.filter_map + (fun conflict_class -> + match List.assoc_opt conflict_class conflict_class_prefix_map with + | Some prefix when not (String.starts_with ~prefix name) -> + Some + ( pkg, + PrefixConflictClassMismatch + (WrongPrefix { conflict_class; required_prefix = prefix }) + ) + | _ -> None) + conflict_classes + + let check_prefix_conflict_class_mismatch ~pkg opam = + let conflict_classes = + OpamFile.OPAM.conflict_class opam |> List.map OpamPackage.Name.to_string + in + let name = OpamPackage.name_to_string pkg in + check_prefix_without_conflict_class ~pkg name conflict_classes + @ check_conflict_class_without_prefix ~pkg name conflict_classes + end + + let check_name_field ~pkg opam = + match OpamFile.OPAM.name_opt opam with + | None -> [] + | Some name -> + if OpamPackage.Name.equal name (OpamPackage.name pkg) then + [ (pkg, UnnecessaryField "name") ] + else [ (pkg, UnmatchedName name) ] + + let check_version_field ~pkg opam = + match OpamFile.OPAM.version_opt opam with + | None -> [] + | Some version -> + if OpamPackage.Version.equal version (OpamPackage.version pkg) then + [ (pkg, UnnecessaryField "version") ] + else [ (pkg, UnmatchedVersion version) ] + + let check_dune_subst ~pkg opam = + let errors = + List.filter_map + (function + | OpamTypes.( + [ (CString "dune", None); (CString "subst", None) ], filter) -> ( + match filter with + | Some (OpamTypes.FIdent ([], var, None)) + when String.equal (OpamVariable.to_string var) "dev" -> + None + | _ -> Some (pkg, DubiousDuneSubst)) + | _ -> None) + opam.OpamFile.OPAM.build + in + errors + + let check_checksums ~pkg opam = + let err ~ctx ~filename msg = + let err = + Printf.sprintf "opam field %s contains %s for %s" ctx msg filename + in + (pkg, WeakChecksum err) + in + let check_one_url ~ctx url = + let filename = + Filename.basename @@ OpamUrl.to_string (OpamFile.URL.url url) + in + let checksums = OpamFile.URL.checksum url in + match checksums with + | [] -> [ err ~ctx ~filename "no checksum" ] + | _ -> + if + List.for_all + (fun hash -> + match OpamHash.kind hash with `MD5 -> true | _ -> false) + checksums + then [ err ~ctx ~filename "only MD5 as checksum" ] + else [] + in + let check_extra_file (basename, hash) = + match OpamHash.kind hash with + | `MD5 -> + let filename = OpamFilename.Base.to_string basename in + [ err ~ctx:"extra-files" ~filename "only MD5 as checksum" ] + | _ -> [] + in + let extra_src_errs = + List.concat + (List.map + (fun (_, url) -> check_one_url ~ctx:"extra-sources" url) + (OpamFile.OPAM.extra_sources opam)) + and url_errs = + Option.default [] + (Option.map (check_one_url ~ctx:"url") (OpamFile.OPAM.url opam)) + and extra_file_errs = + Option.default [] + (Option.map + (fun efs -> List.concat (List.map check_extra_file efs)) + (OpamFile.OPAM.extra_files opam)) + in + url_errs @ extra_file_errs @ extra_src_errs + + let check_no_pin_depends ~pkg opam = + match OpamFile.OPAM.pin_depends opam with + | [] -> [] + | _ -> [ (pkg, PinDepends) ] + + let check_no_extra_files ~pkg opam = + match OpamFile.OPAM.extra_files opam with + | None | Some [] -> [] + | Some _ -> [ (pkg, ExtraFiles) ] + + let get_dune_project_version ~pkg url = + D.with_temp_dir "lint-dune-project-version-" @@ fun dir -> + let res = + OpamProcess.Job.run + @@ OpamRepository.pull_tree + (OpamPackage.to_string pkg) + (OpamFilename.Dir.of_string dir) + (OpamFile.URL.checksum url) + [ OpamFile.URL.url url ] + in + match res with + | OpamTypes.Not_available (_, msg) -> Error msg + | Up_to_date _ | Result _ -> ( + let dune_project = Filename.concat dir "dune-project" in + match + In_channel.input_all + |> In_channel.with_open_text dune_project + |> Sexplib.Sexp.parse + with + | exception Sys_error _ -> Ok None + | Sexplib.Sexp.Done (List [ Atom "lang"; Atom "dune"; Atom version ], _) + -> + Ok (Some version) + | Done _ -> Error "(lang dune ...) is not the first construct" + | Cont _ -> Error "Failed to parse the dune-project file") + + let is_dune name = + OpamPackage.Name.equal name (OpamPackage.Name.of_string "dune") + + let get_dune_constraint opam = + let get_max = function + | None, None -> None + | Some x, None -> Some x + | None, Some x -> Some x + | Some x, Some y when OpamVersionCompare.compare x y >= 0 -> Some x + | Some _, Some y -> Some y + in + let get_min = function + | None, None | Some _, None | None, Some _ -> None + | Some x, Some y when OpamVersionCompare.compare x y >= 0 -> Some y + | Some x, Some _ -> Some x + in + let is_build = ref false in + let rec get_lower_bound = function + | OpamFormula.Atom + (OpamTypes.Constraint ((`Gt | `Geq | `Eq), OpamTypes.FString version)) + -> + Some version + | Atom (Filter (FIdent (_, var, _))) + when String.equal (OpamVariable.to_string var) "build" -> + (* TODO: remove this hack-ish side-effect change setting is_build, in + function to get version lower bound *) + is_build := true; + None + | Empty | Atom (Filter _) | Atom (Constraint _) -> None + | Block x -> get_lower_bound x + | And (x, y) -> get_max (get_lower_bound x, get_lower_bound y) + | Or (x, y) -> get_min (get_lower_bound x, get_lower_bound y) + in + let rec aux = function + | OpamFormula.Atom (pkg, constr) -> + if is_dune pkg then + let v = get_lower_bound constr in + Some (Option.default "" v) + else None + | Empty -> None + | Block x -> aux x + | And (x, y) -> get_max (aux x, aux y) + | Or (x, y) -> get_min (aux x, aux y) + in + (!is_build, aux opam.OpamFile.OPAM.depends) + + let check_dune_constraints ~pkg opam = + match opam.OpamFile.OPAM.url with + | Some url -> + let dune_version = get_dune_project_version ~pkg url in + let is_build, dune_constraint = get_dune_constraint opam in + let errors = + match (dune_constraint, dune_version) with + | _, Error msg -> [ (pkg, FailedToDownload msg) ] + | None, Ok None -> [] + | Some "", _ -> [ (pkg, DuneLowerBoundMissing) ] + | Some _, Ok None -> [ (pkg, DuneProjectMissing) ] + | None, Ok (Some _) -> + if is_dune (OpamPackage.name pkg) then [] + else [ (pkg, DuneDependencyMissing) ] + | Some dep, Ok (Some ver) -> + if OpamVersionCompare.compare dep ver >= 0 then [] + else [ (pkg, BadDuneConstraint (dep, ver)) ] + in + if is_build then (pkg, DuneIsBuild) :: errors else errors + | None -> [] + + let check_maintainer_email ~pkg opam = + let maintainers = OpamFile.OPAM.maintainer opam in + List.filter_map + (fun m -> + if Str.string_match (Str.regexp ".*?") m 0 then None + else Some (pkg, MaintainerEmailMissing m)) + maintainers + + let check_tags ~pkg opam = + (* Check if any of the default tags are present *) + let tags = OpamFile.OPAM.tags opam in + let default_tags = + [ "add topics"; "topics"; "to describe"; "your"; "project" ] + in + let default_tags_present = + List.filter_map + (fun tag -> if List.mem tag tags then Some tag else None) + default_tags + in + match default_tags_present with + | [] -> [] + | _ -> [ (pkg, DefaultTagsPresent default_tags_present) ] + + let opam_lint ~pkg opam = + OpamFileTools.lint ~check_upstream:true opam + |> List.map (fun x -> (pkg, OpamLint x)) + + let is_perm_correct file = + Unix.stat file |> function + | { st_kind = S_REG; st_perm = 0o644; _ } -> true + | { st_kind = S_REG; st_perm = 0o664; _ } -> true + | _ -> false + + let check_package_dir ~repo_dir ~pkg _opam = + let dir = O.path_from_pkg ~repo_dir pkg in + let check_file = function + | "opam" -> + let path = dir // "opam" in + if is_perm_correct path then [] else [ (pkg, ForbiddenPerm path) ] + | file -> [ (pkg, UnexpectedFile file) ] + in + get_files dir |> List.map check_file |> List.concat + + (** [package_name_collision p0 p1] returns true if [p0] is similar to [p1]. + Similarity is defined to be either: + + - Case-insensitive string equality considering underscores ([_]) + and dashes ([-]) to be equal + - A Levenshtein distance within 1/6 of the length of the string (rounding up), + with names of three characters or less ignored as a special case + + As examples, by this relation: + + - [lru-cache] and [lru_cache] collide + - [lru-cache] and [LRU-cache] collide + - [lru-cache] and [cache-lru] do not collide + - [ocaml] and [pcaml] collide + - [ocamlfind] and [ocamlbind] do not collide *) + let package_name_collision p0 p1 = + let dash_underscore p0 p1 = + let f = function '_' -> '-' | c -> c in + let p0 = String.map f p0 in + let p1 = String.map f p1 in + String.equal p0 p1 + in + let levenstein_distance p0 p1 = + let l = String.length p0 in + if l <= 3 then false + else + let k = ((l - 1) / 16) + 2 in + (* Ignore distances of 1, too many false positives: + https://github.com/ocaml/opam-repository/pull/25678 *) + match Mula.Strings.Lev.get_distance ~k p0 p1 with + | None -> false + | Some n when n <= 1 -> false + | Some _ -> true + in + dash_underscore p0 p1 || levenstein_distance p0 p1 + + let check_name_collisions ~pkg packages _opam = + let pkg_name = pkg.OpamPackage.name |> OpamPackage.Name.to_string in + let pkg_name_lower = String.lowercase_ascii pkg_name in + let other_pkgs = + List.filter (fun s -> not @@ String.equal s pkg_name) packages + in + List.filter_map + (fun other_pkg -> + let other_pkg_lower = String.lowercase_ascii other_pkg in + if package_name_collision pkg_name_lower other_pkg_lower then + Some (pkg, NameCollision other_pkg) + else None) + other_pkgs + + let checks ~newly_published ~repo_dir packages = + let newly_published_checks = + [ check_name_collisions packages; Prefix.check_name_restricted_prefix ] + in + let checks = + [ + check_name_field; + check_version_field; + check_dune_subst; + check_dune_constraints; + check_checksums; + check_package_dir ~repo_dir; + opam_lint; + check_maintainer_email; + check_tags; + check_no_pin_depends; + check_no_extra_files; + Prefix.check_prefix_conflict_class_mismatch; + ] + in + if newly_published then checks @ newly_published_checks else checks + + let run_checks ~repo_dir ~pkg ~packages ?(newly_published = false) opam = + checks ~newly_published packages ~repo_dir + |> List.map (fun f -> f ~pkg opam) + |> List.concat + + let parse_error pkg = (pkg, ParseError) +end + +let get_packages repo_dir = + get_files (repo_dir // "packages") |> List.sort String.compare + +let run_package_lint ~newly_published ~repo_dir pkg = + let pkg = OpamPackage.of_string pkg in + let opam_path = O.path_from_pkg ~repo_dir pkg // "opam" in + (* NOTE: We use OpamFile.OPAM.read_from_channel instead of OpamFile.OPAM.file + to prevent the name and version fields being automatically added *) + In_channel.with_open_text opam_path (fun ic -> + let opam = + try Ok (OpamFile.OPAM.read_from_channel ic) + with OpamPp.Bad_format e | OpamPp.Bad_version (e, _) -> Error e + in + match opam with + | Ok opam -> + let packages = get_packages repo_dir in + Checks.run_checks ~repo_dir ~pkg ~packages ~newly_published opam + | Error _ -> [ Checks.parse_error pkg ]) + +let check ~new_pkgs ~changed_pkgs repo_dir = + let changed_errors = + List.map (run_package_lint ~newly_published:false ~repo_dir) changed_pkgs + |> List.concat + in + let new_pkg_errors = + List.map (run_package_lint ~newly_published:true ~repo_dir) new_pkgs + |> List.concat + in + new_pkg_errors @ changed_errors diff --git a/opam-ci-check/lib/lint.mli b/opam-ci-check/lib/lint.mli new file mode 100644 index 00000000..81a26a09 --- /dev/null +++ b/opam-ci-check/lib/lint.mli @@ -0,0 +1,29 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +include module type of Lint_error + +val check : + new_pkgs:string list -> + changed_pkgs:string list -> + string -> + (OpamPackage.t * error) list +(** [check ~new_pkgs ~changed_pkgs opam_repo] is a list of all the errors + detected while linting the [new_pkgs] and [changed_pkgs] in the context of the + opam repository located at [opam_repo]. + + @param new_pkgs Packages which are to be newly published. + @param changed_pkgs Packages that have been updated (e.g., to add maintainers + update dependency versions). + @param opam_repo The path a local opam repository. + + Examples: + + {[ + let passes_all_checks = assert (check ~new_pkgs ~changed_pkgs repo |> List.length = 0) + let failed_some_checks = assert (check ~new_pkgs ~changed_pkgs repo |> List.length > 0) + let messages_for_all_failed_checks = + check ~new_pkgs ~changed_pkgs repo + |> List.map msg_of_error + ]} *) diff --git a/opam-ci-check/lib/lint_error.ml b/opam-ci-check/lib/lint_error.ml new file mode 100644 index 00000000..290837a7 --- /dev/null +++ b/opam-ci-check/lib/lint_error.ml @@ -0,0 +1,163 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +(** Some package name prefixes must be used along with specific conflict classes + + If either a restricted prefix or conflict class exists, then the + corresponding other must also exist. *) +type prefix_conflict_class_mismatch = + | WrongPrefix of { conflict_class : string; required_prefix : string } + | WrongConflictClass of { prefix : string; required_conflict_class : string } + +(** Errors detected during linting. + + Use {!msg_of_error} to produce descriptions the errors *) +type error = + | UnnecessaryField of string + | UnmatchedName of OpamPackage.Name.t + | UnmatchedVersion of OpamPackage.Version.t + | DubiousDuneSubst + | DuneProjectMissing + | DuneDependencyMissing + | DuneLowerBoundMissing + | DuneIsBuild + | BadDuneConstraint of string * string + | UnexpectedFile of string + | ForbiddenPerm of string + | OpamLint of (int * [ `Warning | `Error ] * string) + | MaintainerEmailMissing of string + | FailedToDownload of string + | NameCollision of string + | WeakChecksum of string + | PinDepends + | ExtraFiles + | RestrictedPrefix of string + | PrefixConflictClassMismatch of prefix_conflict_class_mismatch + | ParseError + | DefaultTagsPresent of string list + +(**/**) + +let msg_of_prefix_conflict_class_mismatch ~pkg = function + | WrongPrefix { conflict_class; required_prefix } -> + Printf.sprintf + "Error in %s: package with conflict class '%s' requires name prefix \ + '%s'" + pkg conflict_class required_prefix + | WrongConflictClass { prefix; required_conflict_class } -> + Printf.sprintf + "Error in %s: package with prefix '%s' requires conflict class '%s'" pkg + prefix required_conflict_class + +(**/**) + +(** [msg_of_error (pkg, err)] is a string describing a linting [err] found for the [pkg] *) +let msg_of_error (package, (err : error)) = + let pkg = OpamPackage.to_string package in + match err with + | UnnecessaryField field -> + Printf.sprintf + "Warning in %s: Unnecessary field '%s'. It is suggested to remove it." + pkg field + | UnmatchedName value -> + Printf.sprintf + "Error in %s: The field 'name' that doesn't match its context. Field \ + 'name' has value '%s' but was expected of value '%s'." + pkg + (OpamPackage.Name.to_string value) + (OpamPackage.Name.to_string (OpamPackage.name package)) + | UnmatchedVersion value -> + Printf.sprintf + "Error in %s: The field 'version' that doesn't match its context. \ + Field 'version' has value '%s' but was expected of value '%s'." + pkg + (OpamPackage.Version.to_string value) + (OpamPackage.Version.to_string (OpamPackage.version package)) + | DubiousDuneSubst -> + Printf.sprintf + "Warning in %s: Dubious use of 'dune subst'. 'dune subst' should \ + always only be called with {dev} (i.e. [\"dune\" \"subst\"] {dev}) If \ + your opam file has been autogenerated by dune, you need to upgrade \ + your dune-project to at least (lang dune 2.7)." + pkg + | WeakChecksum msg -> + Printf.sprintf + "Error in %s: Weak checksum algorithm(s) provided. Please use SHA-256 \ + or SHA-512. Details: %s" + pkg msg + | PinDepends -> + Printf.sprintf + "Error in %s: pin-depends present. This is not allowed in the \ + opam-repository." + pkg + | ExtraFiles -> + Printf.sprintf + "Error in %s: extra-files present. This is not allowed in the \ + opam-repository. Please use extra-source instead." + pkg + | FailedToDownload msg -> + Printf.sprintf "Error in %s: Failed to download the archive. Details: %s" + pkg msg + | DuneProjectMissing -> + Printf.sprintf + "Warning in %s: The package seems to use dune but the dune-project \ + file is missing." + pkg + | DuneDependencyMissing -> + Printf.sprintf + "Warning in %s: The package has a dune-project file but no explicit \ + dependency on dune was found." + pkg + | DuneLowerBoundMissing -> + Printf.sprintf + "Warning in %s: The package has a dune dependency without a lower \ + bound." + pkg + | BadDuneConstraint (dep, ver) -> + Printf.sprintf + "Error in %s: Your dune-project file indicates that this package \ + requires at least dune %s but your opam file only requires dune >= \ + %s. Please check which requirement is the right one, and fix the \ + other." + pkg ver dep + | DuneIsBuild -> + Printf.sprintf + "Warning in %s: The package tagged dune as a build dependency. Due to \ + a bug in dune (https://github.com/ocaml/dune/issues/2147) this should \ + never be the case. Please remove the {build} tag from its filter." + pkg + | RestrictedPrefix prefix -> + Printf.sprintf "Warning in %s: package name has restricted prefix '%s'" + pkg prefix + | PrefixConflictClassMismatch mismatch -> + msg_of_prefix_conflict_class_mismatch ~pkg mismatch + | NameCollision other_pkg -> + Printf.sprintf "Warning in %s: Possible name collision with package '%s'" + pkg other_pkg + | UnexpectedFile file -> + Printf.sprintf "Error in %s: Unexpected file in %s/%s" pkg + (Opam_helpers.path_from_pkg ~repo_dir:"" package) + file + | ForbiddenPerm file -> + Printf.sprintf + "Error in %s: Forbidden permission for file %s/%s. All files should \ + have permissions 644." + pkg + (Opam_helpers.path_from_pkg ~repo_dir:"" package) + (Filename.basename file) + | OpamLint warn -> + let warn = OpamFileTools.warns_to_string [ warn ] in + Printf.sprintf "Error in %s: %s" pkg warn + | MaintainerEmailMissing maintainer -> + Printf.sprintf + "Error in %s: Maintainer email missing. Please add a maintainer email \ + to the opam file. Maintainer: %s" + pkg maintainer + | ParseError -> + Printf.sprintf "Error in %s: Failed to parse the opam file" pkg + | DefaultTagsPresent tags -> + Printf.sprintf + "Warning in %s: The package has not replaced the following default, \ + example tags: %s" + pkg (String.concat ", " tags) diff --git a/opam-ci-check/lib/opam_ci_check.ml b/opam-ci-check/lib/opam_ci_check.ml new file mode 100644 index 00000000..ae31d9a4 --- /dev/null +++ b/opam-ci-check/lib/opam_ci_check.ml @@ -0,0 +1,7 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +module Revdeps = Revdeps +module Test = Test +module Lint = Lint diff --git a/opam-ci-check/lib/opam_helpers.ml b/opam-ci-check/lib/opam_helpers.ml new file mode 100644 index 00000000..7869fb77 --- /dev/null +++ b/opam-ci-check/lib/opam_helpers.ml @@ -0,0 +1,10 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +let ( // ) = Filename.concat + +let path_from_pkg ~repo_dir pkg = + repo_dir // "packages" + // OpamPackage.Name.to_string (OpamPackage.name pkg) + // OpamPackage.to_string pkg diff --git a/opam-ci-check/lib/revdeps.ml b/opam-ci-check/lib/revdeps.ml new file mode 100644 index 00000000..4b5a101b --- /dev/null +++ b/opam-ci-check/lib/revdeps.ml @@ -0,0 +1,95 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +let filter_coinstallable st original_package packages = + let universe = + OpamSwitchState.universe st + ~requested:(OpamPackage.Set.union original_package packages) + Query + in + OpamSolver.coinstallable_subset universe original_package packages + +let opam_file_of_package st p = + match OpamSwitchState.opam_opt st p with + | None -> OpamFile.OPAM.create p + | Some o -> + OpamFile.OPAM.( + with_name p.OpamPackage.name (with_version p.OpamPackage.version o)) + +let transitive_revdeps st package_set = + (* Optional dependencies are not transitive: if a optionally depends on b, + and b optionally depends on c, it does not follow that a optionally depends on c. *) + let depopts = false in + (* [with-only] dependencies are also not included in [reverse_dependencies]. *) + try + (* Computes the transitive closure of the reverse dependencies *) + OpamSwitchState.reverse_dependencies ~depopts ~build:true ~post:false + ~installed:false ~unavailable:false st package_set + with Not_found -> + failwith "TODO: Handle packages that are not found in repo" + +(* Optional ([depopt]) and test-only ([with-test]) revdeps are not included in + [transitive_revdeps], but we still want those revdeps that depend on our + target package_set optionally or just for tests *directly*. The sole purpose + of this function is to compute the direct [depopt] and [with-test] revdeps. + + This function adapts the core logic from the non-recursive case of [opam list + --depends-on]. + + See https://github.com/ocaml/opam/blob/b3d2f5c554e6ef3cc736a9f97e252486403e050f/src/client/opamListCommand.ml#L238-L244 *) +let non_transitive_revdeps st package_set = + (* We filter through all packages known to the state, which follows the logic + of the [opam list] command, except that we omit inclusion of merely + installed packages that are not in a repository in the state. This is + because we are only concerned with revdep testing of installable packages, + not of random local things that a user may have installed in their system. + + See https://github.com/ocaml/opam/blob/b3d2f5c554e6ef3cc736a9f97e252486403e050f/src/client/opamCommands.ml#L729-L731 *) + let all_known_packages = st.OpamStateTypes.packages in + let packages_depending_on_target_packages revdep_candidate_pkg = + let dependancy_on = + revdep_candidate_pkg |> opam_file_of_package st + |> OpamPackageVar.all_depends ~test:true ~depopts:true st + |> OpamFormula.verifies + in + OpamPackage.Set.exists dependancy_on package_set + in + OpamPackage.Set.filter packages_depending_on_target_packages + all_known_packages + +let list_revdeps ?(opam_repo = "https://opam.ocaml.org") ?(transitive = true) + pkg = + (* Create local opam root and switch *) + Env.create_local_switch_maybe (Some opam_repo); + OpamConsole.msg "Listing revdeps for %s\n" pkg; + let package = OpamPackage.of_string pkg in + let package_set = OpamPackage.Set.singleton package in + Env.with_unlocked_switch () (fun st -> + let transitive_deps = + if transitive then transitive_revdeps st package_set + else OpamPackage.Set.empty + in + let non_transitive = non_transitive_revdeps st package_set in + OpamPackage.Set.union transitive_deps non_transitive + |> filter_coinstallable st package_set + |> OpamPackage.Set.to_list_map (fun x -> x)) + +let find_latest_versions packages = + let open OpamPackage in + let packages_set = packages |> OpamPackage.Set.of_list in + let versions_map = packages_set |> to_map in + Name.Map.fold + (fun name _versions acc -> + let latest_version = max_version packages_set name in + Set.add latest_version acc) + versions_map Set.empty + |> OpamPackage.Set.to_list_map (fun x -> x) + +module Display = struct + let packages packages = + packages + |> List.iter (fun p -> OpamConsole.msg "%s\n" (OpamPackage.to_string p)); + OpamConsole.msg "Number of reverse dependencies: %d\n" + (List.length packages) +end diff --git a/opam-ci-check/lib/revdeps.mli b/opam-ci-check/lib/revdeps.mli new file mode 100644 index 00000000..a1009221 --- /dev/null +++ b/opam-ci-check/lib/revdeps.mli @@ -0,0 +1,43 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +(** Analyze and test the reverse dependencies of a package. *) + +val list_revdeps : + ?opam_repo:string -> ?transitive:bool -> string -> OpamPackage.t list +(** [list_revdeps pkg] is a list of the transitive reverse dependencies of [pkg]. + + @param opam_repo The package repository to use when calculating + dependencies. Defaults to ["https://opam.ocaml.org"]. + + @param transitive Whether or to list all transitive reverse dependencies. + Defaults to [true]. + + @param pkg The package for which reverse dependencies will be listed, in a + form like ["pkgname.0.0.1"]. + + Examples: + + {[ + let transitive_revdeps = + list_revdeps "cmdliner.1.1.1" + + let nontransitive_revdeps = + list_revdeps ~transitive:false "cmdliner.1.1.1" + + let nontransitive_revdeps_from_custom_repo = + list_revdeps ~opam_repo:"/path/to/my/repo" ~transitive:false "cmdliner.1.1.1" + ]} +*) + +val find_latest_versions : OpamPackage.t list -> OpamPackage.t list +(** [find_latest_versions packages] is a list containing just the latest + versions of each package in [packages]. *) + +module Display : sig + (** Display information about reverse dependencies *) + + val packages : OpamPackage.t list -> unit + (** [packages ps] writes all the packages in [ps] to stdout. *) +end diff --git a/opam-ci-check/lib/test.ml b/opam-ci-check/lib/test.ml new file mode 100644 index 00000000..9c60c5bd --- /dev/null +++ b/opam-ci-check/lib/test.ml @@ -0,0 +1,73 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +module H = Dune_helpers +module D = Dir_helpers + +type error = OpamPackage.t * exn + +let error_to_string : error -> string = + fun (pkg, err) -> + Printf.sprintf "Failed to install %s\nError: %s\n" + (OpamPackage.to_string pkg) + (Printexc.to_string err) + +let pkg_atom : OpamPackage.t -> OpamFormula.atom = + fun pkg -> (OpamPackage.name pkg, Some (`Eq, OpamPackage.version pkg)) + +let test_package_with_opam_in_state st revdep = + OpamConsole.msg "Installing and testing %s\n" (OpamPackage.to_string revdep); + let revdep_atoms = [ pkg_atom revdep ] in + match OpamClient.install st revdep_atoms with + | st' -> + (* Clean-up switch for next test: We remove only the revdep, but not the + target package being tested. *) + ignore (OpamClient.remove st' ~autoremove:true ~force:true revdep_atoms); + None + | exception e -> + (* NOTE: The CI is identifying packages to SKIP, error types, etc. based on + the log output. See + https://github.com/ocurrent/opam-repo-ci/blob/8746f52b479569c0a55904361c9d64b54628b971/service/main.ml#L34. + But, we may be able to do better, since we are not a shell script? *) + Some (revdep, e) + +let test_packages_with_opam target_pkg revdeps_list = + let target = OpamPackage.of_string target_pkg in + match + OpamConsole.confirm "Do you want test %d revdeps?" + (List.length revdeps_list) + with + | true -> + OpamStateConfig.update ?build_test:(Some true) (); + OpamCoreConfig.update ~verbose_level:0 (* run commands quietly *) + ~confirm_level:`unsafe_yes (* Don't prompt for install / remove *) (); + OpamConsole.msg "Installing reverse dependencies with pinned %s\n" + (OpamPackage.to_string target); + Env.with_locked_switch () @@ fun st -> + let st' = OpamClient.install st [ pkg_atom target ] in + revdeps_list |> List.to_seq + |> Seq.filter_map (test_package_with_opam_in_state st') + | _ -> + print_endline "Quitting!"; + Seq.empty + +let test_packages_with_dune opam_repository target_pkg packages = + let target = OpamPackage.of_string target_pkg in + + OpamConsole.msg + "Installing latest version of reverse dependencies with pinned %s\n" + (OpamPackage.to_string target); + let parent = D.create_temp_dir "revdeps_" in + (* FIXME: there can be 1000s of revdeps?! *) + let selected_packages = H.take 3 packages in + (* Prompt before creating the projects *) + Printf.printf "Do you want to generate %d dummy dune project in %s? (y/n): " + (List.length selected_packages) + parent; + (match read_line () with "y" | "Y" -> () | _ -> failwith "Quitting!"); + let dirs = + H.create_dummy_projects parent opam_repository target selected_packages + in + List.iter H.generate_lock_and_build dirs; + Ok () diff --git a/opam-ci-check/lib/test.mli b/opam-ci-check/lib/test.mli new file mode 100644 index 00000000..28702476 --- /dev/null +++ b/opam-ci-check/lib/test.mli @@ -0,0 +1,23 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) + +type error = OpamPackage.t * exn +(** A package and the exception recording its failure to install or pass tests. *) + +val error_to_string : error -> string + +val test_packages_with_opam : string -> OpamPackage.t list -> error Seq.t +(** [test_packages_with_opam package revdeps] is the sequence of errors encountered + while trying to install and test all the packages in [revdeps]. + + Examples: + + {[ + let no_errors_encountered = assert (test_packages_with_opam cmdliner.1.1.1 revdeps |> Seq.length = 0) + let errors_encountered = assert (test_packages_with_opam cmdliner.1.1.1 revdeps |> Seq.length > 0) + ]} + *) + +val test_packages_with_dune : + string -> string -> OpamPackage.t list -> (unit, 'a) result diff --git a/opam-ci-check/prototype.sh b/opam-ci-check/prototype.sh new file mode 100755 index 00000000..130554d1 --- /dev/null +++ b/opam-ci-check/prototype.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -xeuo pipefail + +PKG="${1:-}" + +if [ -z "${PKG}" ]; then + echo "Need a pkg name with version!" + exit 1 +fi + +PREFIX="opam exec --switch=dune-test --" + +VERSION="${PKG#*.}" + +$PREFIX opam repository set-url --strict default ../opam-repository/ + +REVDEPS=$($PREFIX opam list -s --verbose --debug --color=never --depends-on "${PKG}" --coinstallable-with "${PKG}" --installable --all-versions --depopts; + $PREFIX opam list -s --verbose --color=never --depends-on "${PKG}" --coinstallable-with "${PKG}" --installable --all-versions --recursive; + $PREFIX opam list -s --verbose --color=never --depends-on "${PKG}" --coinstallable-with "${PKG}" --installable --all-versions --with-test --depopts) +REVDEPS=$(echo "$REVDEPS"|sort|uniq) + +# $PREFIX opam update --depexts || true + +$PREFIX opam pin add -k version -yn "${PKG}" "${VERSION}" +$PREFIX opam reinstall "${PKG}" # FIXME: CI has a bunch of code to figure out what exactly failed, if so. + +for package in ${REVDEPS}; do + $PREFIX opam reinstall --verbose --with-test "${package}" + break +done diff --git a/opam-ci-check/test/dune b/opam-ci-check/test/dune new file mode 100644 index 00000000..8cb478d6 --- /dev/null +++ b/opam-ci-check/test/dune @@ -0,0 +1,11 @@ +(test + (name test_opam_ci_check) + (package opam-ci-check)) + +(cram + (package opam-ci-check) + (deps + %{bin:opam-ci-check} + ./scripts/setup_repo.sh + ./scripts/setup_sources.sh + (glob_files ./patches/*))) diff --git a/opam-ci-check/test/lint.t b/opam-ci-check/test/lint.t new file mode 100644 index 00000000..1e8afe29 --- /dev/null +++ b/opam-ci-check/test/lint.t @@ -0,0 +1,226 @@ +Setup test opam-repository directory + + $ sh "scripts/setup_repo.sh" + $ git checkout -qb new-branch-1 + +Tests linting of correctly formatted opam packages + + $ git apply "patches/b-correct.patch" + $ git add . + $ git commit -qm b-correct + $ git log --graph --pretty=format:'%s%d' + * b-correct (HEAD -> new-branch-1) + * a-1 (tag: initial-state, master) + $ opam-ci-check lint -r . -c a-1.0.0.2 + Linting opam-repository at $TESTCASE_ROOT/. ... + No errors + $ opam-ci-check lint -r . -c b.0.0.3 + Linting opam-repository at $TESTCASE_ROOT/. ... + No errors + +Setup repo for incorrect b package tests + + $ git reset -q --hard initial-state + $ git apply "patches/b-incorrect-opam.patch" + $ git add packages/ + $ echo "(lang dune 3.16)" > dune-project + $ sh "scripts/setup_sources.sh" b 0.0.2 dune-project + Created tarball b.0.0.2.tgz + Updated checksum for b.0.0.2.tgz in b.0.0.2's opam file + $ sh "scripts/setup_sources.sh" b 0.0.3 dune-project + Created tarball b.0.0.3.tgz + Updated checksum for b.0.0.3.tgz in b.0.0.3's opam file + $ echo "foo" > bar + $ sh "scripts/setup_sources.sh" b 0.0.4 bar + Created tarball b.0.0.4.tgz + Updated checksum for b.0.0.4.tgz in b.0.0.4's opam file + $ echo "(lang dune 3.16)" > dune-project + $ ln -s /tmp/non-existant-link random-link + $ sh "scripts/setup_sources.sh" b 0.0.5 dune-project random-link + Created tarball b.0.0.5.tgz + Updated checksum for b.0.0.5.tgz in b.0.0.5's opam file + $ git commit -qm b-incorrect-opam + $ git log --graph --pretty=format:'%s%d' + * b-incorrect-opam (HEAD -> new-branch-1) + * a-1 (tag: initial-state, master) + + +Test the following: +- [b.0.0.1] is missing the [author] field +- [b.0.0.1] is missing the maintainer's email in the [maintainer] field +- [b.0.0.1] has default, example tags +- [b.0.0.2] has an extra unknown field +- [b.0.0.3] has a pin-depends present, and a conflict class without the required prefix; use of extra-files and a weak checksum algorithm +- [system-b.0.0.1] is using a restricted prefix in its name +- [b.0.0.4] has a missing dune-project file +- [b.0.0.5] has a dune-project file, but no explicit dependency on dune +- [b.0.0.6] has a incorrectly formatted opam file +- [b.0.0.7] has opam lint errors/warnings + + $ opam-ci-check lint -r . -c b.0.0.1 + Linting opam-repository at $TESTCASE_ROOT/. ... + Error in b.0.0.1: warning 25: Missing field 'authors' + Error in b.0.0.1: Maintainer email missing. Please add a maintainer email to the opam file. Maintainer: Jane + Warning in b.0.0.1: The package has not replaced the following default, example tags: topics, project + [1] + $ opam-ci-check lint -r . -c b.0.0.2 + Linting opam-repository at $TESTCASE_ROOT/. ... + Warning in b.0.0.2: Dubious use of 'dune subst'. 'dune subst' should always only be called with {dev} (i.e. ["dune" "subst"] {dev}) If your opam file has been autogenerated by dune, you need to upgrade your dune-project to at least (lang dune 2.7). + Warning in b.0.0.2: The package tagged dune as a build dependency. Due to a bug in dune (https://github.com/ocaml/dune/issues/2147) this should never be the case. Please remove the {build} tag from its filter. + Warning in b.0.0.2: The package has a dune dependency without a lower bound. + Error in b.0.0.2: error 3: File format error in 'unknown-field' at line 11, column 0: Invalid field unknown-field + [1] + $ opam-ci-check lint -r . -c b.0.0.3 + Linting opam-repository at $TESTCASE_ROOT/. ... + Error in b.0.0.3: Your dune-project file indicates that this package requires at least dune 3.16 but your opam file only requires dune >= 3.15.0. Please check which requirement is the right one, and fix the other. + Error in b.0.0.3: Weak checksum algorithm(s) provided. Please use SHA-256 or SHA-512. Details: opam field extra-files contains only MD5 as checksum for 0install.install + Error in b.0.0.3: pin-depends present. This is not allowed in the opam-repository. + Error in b.0.0.3: extra-files present. This is not allowed in the opam-repository. Please use extra-source instead. + Error in b.0.0.3: package with conflict class 'ocaml-host-arch' requires name prefix 'host-arch-' + [1] + $ opam-ci-check lint -r . -c system-b.0.0.1 + Linting opam-repository at $TESTCASE_ROOT/. ... + Error in system-b.0.0.1: package with prefix 'system-' requires conflict class 'ocaml-system' + [1] + $ opam-ci-check lint -r . -c b.0.0.4 + Linting opam-repository at $TESTCASE_ROOT/. ... + Warning in b.0.0.4: The package seems to use dune but the dune-project file is missing. + [1] + $ opam-ci-check lint -r . -c b.0.0.5 + Linting opam-repository at $TESTCASE_ROOT/. ... + Warning in b.0.0.5: The package has a dune-project file but no explicit dependency on dune was found. + [1] + $ opam-ci-check lint -r . -c b.0.0.6 + Linting opam-repository at $TESTCASE_ROOT/. ... + Error in b.0.0.6: Failed to parse the opam file + [1] + $ opam-ci-check lint -r . -c b.0.0.7 + Linting opam-repository at $TESTCASE_ROOT/. ... + Error in b.0.0.7: error 23: Missing field 'maintainer' + Error in b.0.0.7: warning 25: Missing field 'authors' + [1] + +Setup repo for name collision tests + + $ git reset -q --hard initial-state + $ git apply "patches/a_1-name-collision.patch" + $ git add . + $ git commit -qm a_1-name-collision + $ git log --graph --pretty=format:'%s%d' + * a_1-name-collision (HEAD -> new-branch-1) + * a-1 (tag: initial-state, master) + +Tests the package name collision detection by adding a version of a package +[a_1] that conflicts with the existing [a-1] package + + $ opam-ci-check lint -r . -n a_1.0.0.1 + Linting opam-repository at $TESTCASE_ROOT/. ... + Warning in a_1.0.0.1: Possible name collision with package 'a-1' + [1] + +Setup repo for more name collision tests + + $ git checkout -q master + $ git apply "patches/levenshtein-1.patch" + $ git add . + $ git commit -qm levenshtein-1 + $ git checkout -qb new-branch-2 + $ git apply "patches/levenshtein-2.patch" + $ git add . + $ git commit -qm levenshtein-2 + $ git log --graph --pretty=format:'%s%d' + * levenshtein-2 (HEAD -> new-branch-2) + * levenshtein-1 (master) + * a-1 (tag: initial-state) + +Tests the package name collisions detection by adding initial packages [field], +[field1] and [fieldfind] to master, and new packages [fielf], [fielffind], and +[fielffinder] to the new branch to test various positive and negative cases + + $ opam-ci-check lint -r . -n fielf.0.0.1 + Linting opam-repository at $TESTCASE_ROOT/. ... + Warning in fielf.0.0.1: Possible name collision with package 'field1' + [1] + $ opam-ci-check lint -r . -n field1.0.0.2 + Linting opam-repository at $TESTCASE_ROOT/. ... + Warning in field1.0.0.2: Possible name collision with package 'fielf' + [1] + $ opam-ci-check lint -r . -n fieffinder.0.0.1 + Linting opam-repository at $TESTCASE_ROOT/. ... + Warning in fieffinder.0.0.1: Possible name collision with package 'fieffind' + [1] + $ opam-ci-check lint -r . -n fieffind.0.0.1 + Linting opam-repository at $TESTCASE_ROOT/. ... + Warning in fieffind.0.0.1: Possible name collision with package 'fieffinder' + Warning in fieffind.0.0.1: Possible name collision with package 'fieldfind' + [1] + +Setup repo for unnecessary fields tests + + $ git reset -q --hard initial-state + $ git apply "patches/a-1-unnecessary-fields.patch" + $ git add . + $ git commit -qm unnecessary-fields-a-1 + $ git log --graph --pretty=format:'%s%d' + * unnecessary-fields-a-1 (HEAD -> new-branch-2) + * a-1 (tag: initial-state) + +Test presence of unnecessary fields in a-1.0.0.2 package + + $ opam-ci-check lint -r . -c a-1.0.0.2 + Linting opam-repository at $TESTCASE_ROOT/. ... + Warning in a-1.0.0.2: Unnecessary field 'name'. It is suggested to remove it. + Warning in a-1.0.0.2: Unnecessary field 'version'. It is suggested to remove it. + [1] + +Setup repo for unmatched name and version test + + $ git reset -q --hard initial-state + $ git apply "patches/a-1-unmatched-name-version.patch" + $ git add . + $ git commit -qm unmatched-name-version-fields-a-1 + $ git log --graph --pretty=format:'%s%d' + * unmatched-name-version-fields-a-1 (HEAD -> new-branch-2) + * a-1 (tag: initial-state) + +Test presence of unnecessary fields in a-1.0.0.2 package + + $ opam-ci-check lint -r . -c a-1.0.0.2 + Linting opam-repository at $TESTCASE_ROOT/. ... + Error in a-1.0.0.2: The field 'name' that doesn't match its context. Field 'name' has value 'b-1' but was expected of value 'a-1'. + Error in a-1.0.0.2: The field 'version' that doesn't match its context. Field 'version' has value '0.0.1' but was expected of value '0.0.2'. + [1] + +Setup repo for unexpected file + + $ git reset -q --hard initial-state + $ git apply "patches/a-1-unexpected-file.patch" + $ git add . + $ git commit -qm unexpected-file-a-1 + $ git log --graph --pretty=format:'%s%d' + * unexpected-file-a-1 (HEAD -> new-branch-2) + * a-1 (tag: initial-state) + +Test presence of unexpected files in a-1.0.0.2 package + + $ opam-ci-check lint -r . -c a-1.0.0.2 + Linting opam-repository at $TESTCASE_ROOT/. ... + Error in a-1.0.0.2: Unexpected file in packages/a-1/a-1.0.0.2/files + [1] + +Setup repo for Forbidden perm file + + $ git reset -q --hard initial-state + $ chmod 500 packages/a-1/a-1.0.0.2/opam + $ git add . + $ git commit -qm forbidden-perm-file-a-1 + $ git log --graph --pretty=format:'%s%d' + * forbidden-perm-file-a-1 (HEAD -> new-branch-2) + * a-1 (tag: initial-state) + +Test presence of unexpected files in a-1.0.0.2 package + + $ opam-ci-check lint -r . -c a-1.0.0.2 + Linting opam-repository at $TESTCASE_ROOT/. ... + Error in a-1.0.0.2: Forbidden permission for file packages/a-1/a-1.0.0.2/opam. All files should have permissions 644. + [1] diff --git a/opam-ci-check/test/patches/a-1-insignificant-change.patch b/opam-ci-check/test/patches/a-1-insignificant-change.patch new file mode 100644 index 00000000..a69100fd --- /dev/null +++ b/opam-ci-check/test/patches/a-1-insignificant-change.patch @@ -0,0 +1,26 @@ +diff --git a/packages/a-1/a-1.0.0.1/opam b/packages/a-1/a-1.0.0.1/opam +index 5246a83..c17a1ba 100644 +--- a/packages/a-1/a-1.0.0.1/opam ++++ b/packages/a-1/a-1.0.0.1/opam +@@ -1,7 +1,7 @@ + opam-version: "2.0" + synopsis: "Synopsis" + description: "Description" +-maintainer: "Maintainer" ++maintainer: "Another maintainer" + author: "Author" + license: "Apache-2.0" + homepage: "https://github.com/ocurrent/opam-repo-ci" +diff --git a/packages/a-1/a-1.0.0.2/opam b/packages/a-1/a-1.0.0.2/opam +index 5246a83..c018682 100644 +--- a/packages/a-1/a-1.0.0.2/opam ++++ b/packages/a-1/a-1.0.0.2/opam +@@ -1,7 +1,7 @@ + opam-version: "2.0" + synopsis: "Synopsis" + description: "Description" +-maintainer: "Maintainer" ++maintainer: "Someone else" + author: "Author" + license: "Apache-2.0" + homepage: "https://github.com/ocurrent/opam-repo-ci" diff --git a/opam-ci-check/test/patches/a-1-unexpected-file.patch b/opam-ci-check/test/patches/a-1-unexpected-file.patch new file mode 100644 index 00000000..9b88ea06 --- /dev/null +++ b/opam-ci-check/test/patches/a-1-unexpected-file.patch @@ -0,0 +1,3 @@ +diff --git a/packages/a-1/a-1.0.0.2/files/.keep b/packages/a-1/a-1.0.0.2/files/.keep +new file mode 100644 +index 0000000..e69de29 diff --git a/opam-ci-check/test/patches/a-1-unmatched-name-version.patch b/opam-ci-check/test/patches/a-1-unmatched-name-version.patch new file mode 100644 index 00000000..be7735fa --- /dev/null +++ b/opam-ci-check/test/patches/a-1-unmatched-name-version.patch @@ -0,0 +1,10 @@ +diff --git a/packages/a-1/a-1.0.0.2/opam b/packages/a-1/a-1.0.0.2/opam +index 5246a83..ff2f2ad 100644 +--- a/packages/a-1/a-1.0.0.2/opam ++++ b/packages/a-1/a-1.0.0.2/opam +@@ -10,3 +10,5 @@ dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" + doc: "https://ocurrent.github.io/ocurrent/" + build: [] + depends: [] ++name: "b-1" ++version: "0.0.1" diff --git a/opam-ci-check/test/patches/a-1-unnecessary-fields.patch b/opam-ci-check/test/patches/a-1-unnecessary-fields.patch new file mode 100644 index 00000000..92015c70 --- /dev/null +++ b/opam-ci-check/test/patches/a-1-unnecessary-fields.patch @@ -0,0 +1,10 @@ +diff --git a/packages/a-1/a-1.0.0.2/opam b/packages/a-1/a-1.0.0.2/opam +index 5246a83..e1b8d2c 100644 +--- a/packages/a-1/a-1.0.0.2/opam ++++ b/packages/a-1/a-1.0.0.2/opam +@@ -10,3 +10,5 @@ dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" + doc: "https://ocurrent.github.io/ocurrent/" + build: [] + depends: [] ++name: "a-1" ++version: "0.0.2" diff --git a/opam-ci-check/test/patches/a-1.patch b/opam-ci-check/test/patches/a-1.patch new file mode 100644 index 00000000..db9cb0af --- /dev/null +++ b/opam-ci-check/test/patches/a-1.patch @@ -0,0 +1,50 @@ +From 87954d1c1f2ca2814915ca0e46405f34ae697032 Mon Sep 17 00:00:00 2001 +From: benmandrew +Date: Wed, 21 Feb 2024 13:25:54 +0100 +Subject: [PATCH] a + +--- + packages/a-1/a-1.0.0.1/opam | 12 ++++++++++++ + packages/a-1/a-1.0.0.2/opam | 12 ++++++++++++ + 2 files changed, 24 insertions(+) + create mode 100644 packages/a-1/a-1.0.0.1/opam + create mode 100644 packages/a-1/a-1.0.0.2/opam + +diff --git a/packages/a-1/a-1.0.0.1/opam b/packages/a-1/a-1.0.0.1/opam +new file mode 100644 +index 0000000..58229e8 +--- /dev/null ++++ b/packages/a-1/a-1.0.0.1/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] +diff --git a/packages/a-1/a-1.0.0.2/opam b/packages/a-1/a-1.0.0.2/opam +new file mode 100644 +index 0000000..58229e8 +--- /dev/null ++++ b/packages/a-1/a-1.0.0.2/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] +-- +2.43.0 diff --git a/opam-ci-check/test/patches/a_1-name-collision.patch b/opam-ci-check/test/patches/a_1-name-collision.patch new file mode 100644 index 00000000..1af52a35 --- /dev/null +++ b/opam-ci-check/test/patches/a_1-name-collision.patch @@ -0,0 +1,30 @@ +From 87954d1c1f2ca2814915ca0e46405f34ae697032 Mon Sep 17 00:00:00 2001 +From: benmandrew +Date: Wed, 21 Feb 2024 13:25:54 +0100 +Subject: [PATCH] a + +--- + packages/a_1/a_1.0.0.1/opam | 12 ++++++++++++ + 2 files changed, 24 insertions(+) + create mode 100644 packages/a_1/a_1.0.0.1/opam + +diff --git a/packages/a_1/a_1.0.0.1/opam b/packages/a_1/a_1.0.0.1/opam +new file mode 100644 +index 0000000..58229e8 +--- /dev/null ++++ b/packages/a_1/a_1.0.0.1/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] +-- +2.43.0 diff --git a/opam-ci-check/test/patches/b-correct.patch b/opam-ci-check/test/patches/b-correct.patch new file mode 100644 index 00000000..0dcf52df --- /dev/null +++ b/opam-ci-check/test/patches/b-correct.patch @@ -0,0 +1,76 @@ +From 00dbea20350d84b089ff1c1014252fa31babcb2a Mon Sep 17 00:00:00 2001 +From: benmandrew +Date: Mon, 19 Feb 2024 14:25:05 +0100 +Subject: [PATCH] b + +--- + packages/b/b.0.0.1/opam | 13 +++++++++++++ + packages/b/b.0.0.2/opam | 13 +++++++++++++ + packages/b/b.0.0.3/opam | 13 +++++++++++++ + 3 files changed, 39 insertions(+) + create mode 100644 packages/b/b.0.0.1/opam + create mode 100644 packages/b/b.0.0.2/opam + create mode 100644 packages/b/b.0.0.3/opam + +diff --git a/packages/b/b.0.0.1/opam b/packages/b/b.0.0.1/opam +new file mode 100644 +index 0000000..69040f4 +--- /dev/null ++++ b/packages/b/b.0.0.1/opam +@@ -0,0 +1,14 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [ ++ "a-1" {>= "0.0.1"} ++] +diff --git a/packages/b/b.0.0.2/opam b/packages/b/b.0.0.2/opam +new file mode 100644 +index 0000000..1581508 +--- /dev/null ++++ b/packages/b/b.0.0.2/opam +@@ -0,0 +1,14 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [ ++ "a-1" {>= "0.0.2"} ++] +diff --git a/packages/b/b.0.0.3/opam b/packages/b/b.0.0.3/opam +new file mode 100644 +index 0000000..5ec1dc4 +--- /dev/null ++++ b/packages/b/b.0.0.3/opam +@@ -0,0 +1,14 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [ ++ "a-1" {< "0.0.2"} ++] +-- +2.43.0 diff --git a/opam-ci-check/test/patches/b-incorrect-opam.patch b/opam-ci-check/test/patches/b-incorrect-opam.patch new file mode 100644 index 00000000..d05f0296 --- /dev/null +++ b/opam-ci-check/test/patches/b-incorrect-opam.patch @@ -0,0 +1,186 @@ +diff --git a/packages/b/b.0.0.1/opam b/packages/b/b.0.0.1/opam +new file mode 100644 +index 0000000..3697498 +--- /dev/null ++++ b/packages/b/b.0.0.1/opam +@@ -0,0 +1,14 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Jane" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++tags: ["topics" "sample" "project"] ++build: [] ++depends: [ ++ "a-1" {>= "0.0.1"} ++] +diff --git a/packages/b/b.0.0.2/opam b/packages/b/b.0.0.2/opam +new file mode 100644 +index 0000000..a47aabf +--- /dev/null ++++ b/packages/b/b.0.0.2/opam +@@ -0,0 +1,22 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++unknown-field : "Unknown" ++build: [ ++ ["dune" "subst"] ++] ++url { ++ src: "b.0.0.2.tgz" ++ checksum: ["sha256=e26dfd7fa9731d0d43239c8b7d36bd3d79b0f709f1cdfa2ada7a7ab5d79d1911"] ++} ++depends: [ ++ "a-1" {>= "0.0.2"} ++ "dune" {build} ++] +diff --git a/packages/b/b.0.0.3/opam b/packages/b/b.0.0.3/opam +new file mode 100644 +index 0000000..5bab63e +--- /dev/null ++++ b/packages/b/b.0.0.3/opam +@@ -0,0 +1,24 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++url { ++ src: "b.0.0.3.tgz" ++ checksum: ["sha256=dbfe6f3bf3f62bcbf62fbf0e4eac1741dad592e613dbfe8b06fd359ab5702ee8"] ++} ++depends: [ ++ "a-1" {< "0.0.2"} ++ "dune" {>= "3.15.0"} ++] ++conflict-class: [ ++ "ocaml-host-arch" ++] ++extra-files: ["0install.install" "md5=db6ee7a35da5d98136e5a56bad08496e"] ++pin-depends: [ "foo.~dev" "git+https://github.com/bar/foo" ] +diff --git a/packages/system-b/system-b.0.0.1/opam b/packages/system-b/system-b.0.0.1/opam +new file mode 100644 +index 0000000..0808e56 +--- /dev/null ++++ b/packages/system-b/system-b.0.0.1/opam +@@ -0,0 +1,14 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [ ++ "a-1" {>= "0.0.2"} ++] +diff --git a/packages/b/b.0.0.4/opam b/packages/b/b.0.0.4/opam +new file mode 100644 +index 0000000..5bab63e +--- /dev/null ++++ b/packages/b/b.0.0.4/opam +@@ -0,0 +1,19 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++url { ++ src: "b.0.0.4.tgz" ++ checksum: ["sha256=f1280067914e74e776bbdd0cf630c888305dda08e77811a47555c74c6a57a89b"] ++} ++depends: [ ++ "a-1" {< "0.0.2"} ++ "dune" {>= "3.15.0"} ++] +diff --git a/packages/b/b.0.0.5/opam b/packages/b/b.0.0.5/opam +new file mode 100644 +index 0000000..5bab63e +--- /dev/null ++++ b/packages/b/b.0.0.5/opam +@@ -0,0 +1,18 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++url { ++ src: "b.0.0.5.tgz" ++ checksum: ["sha256=dbfe6f3bf3f62bcbf62fbf0e4eac1741dad592e613dbfe8b06fd359ab5702ee8"] ++} ++depends: [ ++ "a-1" {< "0.0.2"} ++] +diff --git a/packages/b/b.0.0.6/opam b/packages/b/b.0.0.6/opam +new file mode 100644 +index 0000000..5bab63e +--- /dev/null ++++ b/packages/b/b.0.0.6/opam +@@ -0,0 +1,14 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/ ++build: [] ++depends: [ ++ "a-1" {< "0.0.2"} ++] +diff --git a/packages/b/b.0.0.7/opam b/packages/b/b.0.0.7/opam +new file mode 100644 +index 0000000..5bab63e +--- /dev/null ++++ b/packages/b/b.0.0.7/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [ ++ "a-1" {< "0.0.2"} ++] + diff --git a/opam-ci-check/test/patches/b-significant-change.patch b/opam-ci-check/test/patches/b-significant-change.patch new file mode 100644 index 00000000..154e2f1b --- /dev/null +++ b/opam-ci-check/test/patches/b-significant-change.patch @@ -0,0 +1,11 @@ +diff --git a/packages/b/b.0.0.1/opam b/packages/b/b.0.0.1/opam +index 6a903ae..0808e56 100644 +--- a/packages/b/b.0.0.1/opam ++++ b/packages/b/b.0.0.1/opam +@@ -10,5 +10,5 @@ dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" + doc: "https://ocurrent.github.io/ocurrent/" + build: [] + depends: [ +- "a-1" {>= "0.0.1"} ++ "a-1" {>= "0.0.2"} + ] diff --git a/opam-ci-check/test/patches/levenshtein-1.patch b/opam-ci-check/test/patches/levenshtein-1.patch new file mode 100644 index 00000000..2fa1c31d --- /dev/null +++ b/opam-ci-check/test/patches/levenshtein-1.patch @@ -0,0 +1,54 @@ +diff --git a/packages/field/field.0.0.1/opam b/packages/field/field.0.0.1/opam +new file mode 100644 +index 0000000..5246a83 +--- /dev/null ++++ b/packages/field/field.0.0.1/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer" ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] +diff --git a/packages/field1/field1.0.0.1/opam b/packages/field1/field1.0.0.1/opam +new file mode 100644 +index 0000000..5246a83 +--- /dev/null ++++ b/packages/field1/field1.0.0.1/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer" ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] +diff --git a/packages/fieldfind/fieldfind.0.0.1/opam b/packages/fieldfind/fieldfind.0.0.1/opam +new file mode 100644 +index 0000000..5246a83 +--- /dev/null ++++ b/packages/fieldfind/fieldfind.0.0.1/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer" ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] diff --git a/opam-ci-check/test/patches/levenshtein-2.patch b/opam-ci-check/test/patches/levenshtein-2.patch new file mode 100644 index 00000000..739ec102 --- /dev/null +++ b/opam-ci-check/test/patches/levenshtein-2.patch @@ -0,0 +1,72 @@ +diff --git a/packages/fieffind/fieffind.0.0.1/opam b/packages/fieffind/fieffind.0.0.1/opam +new file mode 100644 +index 0000000..5246a83 +--- /dev/null ++++ b/packages/fieffind/fieffind.0.0.1/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] +diff --git a/packages/fieffinder/fieffinder.0.0.1/opam b/packages/fieffinder/fieffinder.0.0.1/opam +new file mode 100644 +index 0000000..5246a83 +--- /dev/null ++++ b/packages/fieffinder/fieffinder.0.0.1/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] +diff --git a/packages/field1/field1.0.0.2/opam b/packages/field1/field1.0.0.2/opam +new file mode 100644 +index 0000000..5246a83 +--- /dev/null ++++ b/packages/field1/field1.0.0.2/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] +diff --git a/packages/fielf/fielf.0.0.1/opam b/packages/fielf/fielf.0.0.1/opam +new file mode 100644 +index 0000000..5246a83 +--- /dev/null ++++ b/packages/fielf/fielf.0.0.1/opam +@@ -0,0 +1,12 @@ ++opam-version: "2.0" ++synopsis: "Synopsis" ++description: "Description" ++maintainer: "Maintainer " ++author: "Author" ++license: "Apache-2.0" ++homepage: "https://github.com/ocurrent/opam-repo-ci" ++bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" ++dev-repo: "git+https://github.com/ocurrent/opam-repo-ci.git" ++doc: "https://ocurrent.github.io/ocurrent/" ++build: [] ++depends: [] diff --git a/opam-ci-check/test/scripts/setup_repo.sh b/opam-ci-check/test/scripts/setup_repo.sh new file mode 100644 index 00000000..5b9d6e53 --- /dev/null +++ b/opam-ci-check/test/scripts/setup_repo.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +git init -q . +git config --local user.email test@test.com +git config --local user.name Test +git checkout -qb master +git apply "patches/a-1.patch" +touch repo +git add . +git commit -qm a-1 +git tag 'initial-state' diff --git a/opam-ci-check/test/scripts/setup_sources.sh b/opam-ci-check/test/scripts/setup_sources.sh new file mode 100644 index 00000000..49a1e3f6 --- /dev/null +++ b/opam-ci-check/test/scripts/setup_sources.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env sh + +# Generate a tarball from the given files and update the sha256 in the opam file + +set -eu + +if [ "$#" -lt 3 ]; then + echo "Usage: $0 " + exit 1 +fi + +package_name=$1 +package_version=$2 +release_name="${package_name}.${package_version}" +tarball_name="${release_name}.tgz" +shift 2 +tar -czf "${tarball_name}" "$@" +echo "Created tarball ${tarball_name}" +# Get the sha256 of the tarball +if command -v sha256sum > /dev/null; then + SHA=$(sha256sum "${tarball_name}" | cut -d ' ' -f 1) +else + SHA=$(shasum -a 256 "${tarball_name}" | cut -d ' ' -f 1) +fi +sed "s/sha256=[0-9a-f]*/sha256=${SHA}/g" "packages/${package_name}/${release_name}/opam" > opam.new +mv opam.new "packages/${package_name}/${release_name}/opam" +echo "Updated checksum for ${tarball_name} in ${release_name}'s opam file" diff --git a/opam-ci-check/test/test_opam_ci_check.ml b/opam-ci-check/test/test_opam_ci_check.ml new file mode 100644 index 00000000..6fc72e78 --- /dev/null +++ b/opam-ci-check/test/test_opam_ci_check.ml @@ -0,0 +1,3 @@ +(* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2024 Puneeth Chaganti , Shon Feder , Tarides + *) diff --git a/opam-repo-ci-api.opam b/opam-repo-ci-api.opam index 65bccebe..c71a13bf 100644 --- a/opam-repo-ci-api.opam +++ b/opam-repo-ci-api.opam @@ -11,13 +11,27 @@ license: "Apache-2.0" homepage: "https://github.com/ocurrent/opam-repo-ci" bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" depends: [ - "dune" {>= "3.7"} + "dune" {>= "3.16"} "ocaml" {>= "4.14.1"} "current_rpc" "capnp" {>= "3.4.0"} "capnp-rpc-lwt" "dockerfile" "odoc" {with-doc} + "current" {>= "0.6.6"} + "current_docker" + "current_github" + "current_ocluster" + "dockerfile-opam" {>= "6.3.0"} + "logs" + "mula" {>= "0.1.2"} + "obuilder-spec" + "ocaml-version" {>= "3.6.9"} + "opam-format" {>= "2.3.0~alpha1"} + "opam-state" {>= "2.3.0~alpha1"} + "ppx_deriving" {>= "5.2.1"} + "ppx_deriving_yojson" + "sexplib" {>= "v0.16.0"} ] conflicts: [ "conduit-lwt-unix" {< "4.0.1"} diff --git a/opam-repo-ci-client.opam b/opam-repo-ci-client.opam index 718b48f1..d2021d34 100644 --- a/opam-repo-ci-client.opam +++ b/opam-repo-ci-client.opam @@ -13,7 +13,7 @@ bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" depends: [ "logs" "fmt" {>= "0.8.7"} - "dune" {>= "3.7"} + "dune" {>= "3.16"} "ocaml" {>= "4.14.1"} "current_rpc" "capnp-rpc-unix" diff --git a/opam-repo-ci-service.opam b/opam-repo-ci-service.opam index a4720c9e..6e787a32 100644 --- a/opam-repo-ci-service.opam +++ b/opam-repo-ci-service.opam @@ -16,11 +16,11 @@ depends: [ "prometheus" {>= "1.2"} # needed for ocurrent "ppx_sexp_conv" "ppx_deriving_yojson" - "ppx_deriving" + "ppx_deriving" {with-test} "mirage-crypto-rng" {>= "0.11.0"} "logs" "fmt" {>= "0.8.7"} - "dune" {>= "3.7"} + "dune" {>= "3.16"} "ocaml" {>= "4.14.1"} "current_incr" {>= "0.6.0"} # see https://github.com/ocurrent/opam-repo-ci/pull/161 "current" {>= "0.6.6"} @@ -35,7 +35,6 @@ depends: [ "memtrace" {>= "0.1.1"} "capnp-rpc-lwt" {>= "0.8.0"} "capnp-rpc-unix" {>= "0.8.0"} - "opam-repo-ci-api" "opam-state" {>= "2.3.0~alpha1"} "opam-format" {>= "2.3.0~alpha1"} "opam-file-format" {>= "2.1.2"} @@ -43,17 +42,15 @@ depends: [ "conf-libev" "dockerfile" {>= "6.3.0"} "dockerfile-opam" {>= "6.3.0"} - "ocaml-version" {>= "2.4.0"} + "ocaml-version" {>= "3.6.9"} "mula" {>= "0.1.2"} "alcotest" {>= "1.0.0" & with-test} "alcotest-lwt" {>= "1.0.1" & with-test} "odoc" {with-doc} - # pin-depends + # Internal dependencies "opam-ci-check" -] -pin-depends: [ - "opam-ci-check.0.1~dev" "git+https://github.com/ocurrent/opam-ci-check.git#c6f883f4500bd69755bcf884a95ccac298cc8c6b" + "opam-repo-ci-api" ] conflicts: [ "ocaml-migrate-parsetree" {= "1.7.1"} diff --git a/opam-repo-ci-web.opam b/opam-repo-ci-web.opam index 6646eb17..51efe7ec 100644 --- a/opam-repo-ci-web.opam +++ b/opam-repo-ci-web.opam @@ -11,7 +11,7 @@ license: "Apache-2.0" homepage: "https://github.com/ocurrent/opam-repo-ci" bug-reports: "https://github.com/ocurrent/opam-repo-ci/issues" depends: [ - "dune" {>= "3.7"} + "dune" {>= "3.16"} "yojson" "logs" "fmt" {>= "0.8.7"} diff --git a/test/dune b/test/dune index 536a121c..d6d4e837 100644 --- a/test/dune +++ b/test/dune @@ -8,17 +8,22 @@ (glob_files ./patches/*))) (cram + (package opam-repo-ci-service) (deps %{bin:opam-repo-ci-local} ./scripts/setup_repo.sh (glob_files ./patches/*))) (rule - (with-stdout-to - specs.output - (run ./test.exe --dump-specs))) + (package opam-repo-ci-service) + (alias runtest) + (action + (with-stdout-to + specs.output + (run ./test.exe --dump-specs)))) (rule + (package opam-repo-ci-service) (alias runtest) (action (diff specs.expected specs.output)))