├── .github ├── scripts │ ├── e2e.py │ ├── integration.py │ └── tag_check.py └── workflows │ ├── ci.yaml │ └── release.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── config.md ├── installation.md └── usage.md ├── dummy-pkg ├── .Rbuildignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── R │ └── lib.R └── dummy.Rproj ├── dummy.tar.gz ├── example_projects ├── archive │ ├── rproject.toml │ └── rv.lock ├── big │ └── rproject.toml ├── custom-lib-path │ └── rproject.toml ├── cyclic-suggests │ ├── README.md │ └── rproject.toml ├── git-dep-branch │ └── rproject.toml ├── git-dep-tag │ └── rproject.toml ├── local-deps │ └── rproject.toml ├── local-tarball-dep │ └── rproject.toml ├── multi-repo │ └── rproject.toml ├── no-lockfile │ └── rproject.toml ├── package-upgrade │ ├── rproject.toml │ └── rv.lock ├── private-git-dep │ └── rproject.toml ├── project-upgrade │ ├── rproject.toml │ └── rv.lock ├── r-universe │ └── rproject.toml ├── rspm-cran │ └── rproject.toml ├── simple │ └── rproject.toml ├── trailing-slash │ └── rproject.toml └── url-dep │ └── rproject.toml ├── justfile ├── scripts └── install.sh └── src ├── activate.rs ├── add.rs ├── cache ├── disk.rs ├── info.rs ├── mod.rs └── utils.rs ├── cli ├── commands │ ├── init.rs │ ├── migrate.rs │ └── mod.rs ├── context.rs ├── mod.rs └── utils.rs ├── config.rs ├── consts.rs ├── fs.rs ├── git ├── local.rs ├── mod.rs ├── reference.rs ├── remote.rs └── url.rs ├── http.rs ├── lib.rs ├── library.rs ├── lockfile.rs ├── main.rs ├── package ├── builtin.rs ├── description.rs ├── mod.rs ├── parser.rs ├── remotes.rs ├── snapshots │ ├── rv__package__remotes__tests__can_parse_remotes-10.snap │ ├── rv__package__remotes__tests__can_parse_remotes-11.snap │ ├── rv__package__remotes__tests__can_parse_remotes-12.snap │ ├── rv__package__remotes__tests__can_parse_remotes-13.snap │ ├── rv__package__remotes__tests__can_parse_remotes-14.snap │ ├── rv__package__remotes__tests__can_parse_remotes-15.snap │ ├── rv__package__remotes__tests__can_parse_remotes-16.snap │ ├── rv__package__remotes__tests__can_parse_remotes-17.snap │ ├── rv__package__remotes__tests__can_parse_remotes-18.snap │ ├── rv__package__remotes__tests__can_parse_remotes-2.snap │ ├── rv__package__remotes__tests__can_parse_remotes-3.snap │ ├── rv__package__remotes__tests__can_parse_remotes-4.snap │ ├── rv__package__remotes__tests__can_parse_remotes-5.snap │ ├── rv__package__remotes__tests__can_parse_remotes-6.snap │ ├── rv__package__remotes__tests__can_parse_remotes-7.snap │ ├── rv__package__remotes__tests__can_parse_remotes-8.snap │ ├── rv__package__remotes__tests__can_parse_remotes-9.snap │ └── rv__package__remotes__tests__can_parse_remotes.snap └── version.rs ├── project_summary.rs ├── r_cmd.rs ├── renv.rs ├── repository.rs ├── repository_urls.rs ├── resolver ├── dependency.rs ├── mod.rs ├── result.rs ├── sat.rs └── snapshots │ ├── rv__resolver__dependency__test__R Universe arrow API parse.snap │ ├── rv__resolver__dependency__test__R Universe scicalc API parse.snap │ ├── rv__resolver__tests__case_sensitive.txt.snap │ ├── rv__resolver__tests__conflict_dep_requirement.txt.snap │ ├── rv__resolver__tests__conflict_dep_requirement2.txt.snap │ ├── rv__resolver__tests__conflict_dep_requirement3.txt.snap │ ├── rv__resolver__tests__dep_force_source_lockfile.txt.snap │ ├── rv__resolver__tests__dep_force_source_no_lockfile.txt.snap │ ├── rv__resolver__tests__dep_force_source_override_repo.txt.snap │ ├── rv__resolver__tests__dependencies_only.txt.snap │ ├── rv__resolver__tests__different_repo_from_lockfile.txt.snap │ ├── rv__resolver__tests__dplyr_no_lockfile.txt.snap │ ├── rv__resolver__tests__dupe_imports_linktsto.txt.snap │ ├── rv__resolver__tests__git_pkg_with_remote_dep.txt.snap │ ├── rv__resolver__tests__git_pkg_with_remote_dep_overridden.txt.snap │ ├── rv__resolver__tests__git_pkg_wrong_name.txt.snap │ ├── rv__resolver__tests__higher_r_version.txt.snap │ ├── rv__resolver__tests__local_dep.txt.snap │ ├── rv__resolver__tests__local_dep_not_found.txt.snap │ ├── rv__resolver__tests__local_dep_wrong_name.txt.snap │ ├── rv__resolver__tests__multi-repo-combination-no-suggests.txt.snap │ ├── rv__resolver__tests__multi-repo-combination-suggests-complete.txt.snap │ ├── rv__resolver__tests__multi-repo-combination-suggests-failure.txt.snap │ ├── rv__resolver__tests__path.txt.snap │ ├── rv__resolver__tests__pkg_listed_in_config_missing_from_remote.txt.snap │ ├── rv__resolver__tests__r_universe_lockfile.txt.snap │ ├── rv__resolver__tests__r_universe_no_lockfile.txt.snap │ ├── rv__resolver__tests__recommended.txt.snap │ ├── rv__resolver__tests__recommended_from_repo_w_lockfile.txt.snap │ ├── rv__resolver__tests__recommended_pkg_specified.txt.snap │ ├── rv__resolver__tests__recommended_pkg_specified_from_lockfile.txt.snap │ ├── rv__resolver__tests__repo_priority_order.txt.snap │ ├── rv__resolver__tests__simple_lockfile.txt.snap │ ├── rv__resolver__tests__simple_no_lockfile.txt.snap │ ├── rv__resolver__tests__suggestions.txt.snap │ ├── rv__resolver__tests__unmet_version_req.txt.snap │ ├── rv__resolver__tests__url-dep.txt.snap │ └── rv__resolver__tests__valid_from_multiple_repos.txt.snap ├── snapshots ├── rv__add__tests__add_remove.snap ├── rv__renv__tests__renv_resolver.snap ├── rv__resolver__tests__multi-repo-combination-no-suggests.txt.snap ├── rv__resolver__tests__multi-repo-combination-suggests-complete.txt.snap └── rv__resolver__tests__multi-repo-combination-suggests-failure.txt.snap ├── sync ├── build_plan.rs ├── changes.rs ├── errors.rs ├── handler.rs ├── link.rs ├── mod.rs └── sources │ ├── git.rs │ ├── local.rs │ ├── mod.rs │ ├── repositories.rs │ └── url.rs ├── system_info.rs ├── system_req.rs ├── tests ├── descriptions │ ├── clindata.DESCRIPTION │ ├── dplyr.DESCRIPTION │ ├── gsm.DESCRIPTION │ ├── gsm.app.DESCRIPTION │ ├── missing.remote.DESCRIPTION │ └── shinytest2.DESCRIPTION ├── invalid_config │ ├── bad_git_url.toml │ ├── bad_repo_url.toml │ └── bad_repo_url2.toml ├── package_files │ ├── a2-ai-universe.PACKAGE │ ├── cran-binary.PACKAGE │ ├── gh-pkg-mirror.PACKAGE │ ├── posit-src.PACKAGE │ ├── test_repo1.PACKAGE │ └── test_repo2.PACKAGE ├── r_universe │ ├── arrow.api │ └── osinfo.api ├── renv │ └── renv.lock ├── resolution │ ├── case_sensitive.txt │ ├── conflict_dep_requirement.txt │ ├── conflict_dep_requirement2.txt │ ├── conflict_dep_requirement3.txt │ ├── dep_force_source_lockfile.txt │ ├── dep_force_source_no_lockfile.txt │ ├── dep_force_source_override_repo.txt │ ├── dependencies_only.txt │ ├── different_repo_from_lockfile.txt │ ├── dplyr_no_lockfile.txt │ ├── dupe_imports_linktsto.txt │ ├── git_pkg_with_remote_dep.txt │ ├── git_pkg_with_remote_dep_overridden.txt │ ├── git_pkg_wrong_name.txt │ ├── higher_r_version.txt │ ├── local_dep.txt │ ├── local_dep_not_found.txt │ ├── local_dep_wrong_name.txt │ ├── multi-repo-combination-no-suggests.txt │ ├── multi-repo-combination-suggests-complete.txt │ ├── multi-repo-combination-suggests-failure.txt │ ├── path.txt │ ├── pkg_listed_in_config_missing_from_remote.txt │ ├── r_universe_lockfile.txt │ ├── r_universe_no_lockfile.txt │ ├── recommended.txt │ ├── recommended_from_repo_w_lockfile.txt │ ├── recommended_pkg_specified.txt │ ├── recommended_pkg_specified_from_lockfile.txt │ ├── repo_priority_order.txt │ ├── simple_lockfile.txt │ ├── simple_no_lockfile.txt │ ├── suggestions.txt │ ├── unmet_version_req.txt │ ├── url-dep.txt │ └── valid_from_multiple_repos.txt └── valid_config │ └── all_fields.toml └── utils.rs /.github/scripts/e2e.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | import subprocess 4 | 5 | INIT_FOLDER = "init" 6 | CONFIG_FILE = "rproject.toml" 7 | RV_REPO_1 = "{ alias = 'repo1', url = 'https://a2-ai.github.io/rv-test-repo/repo1'}" 8 | RV_REPO_2 = "{ alias = 'repo2', url = 'https://a2-ai.github.io/rv-test-repo/repo2'}" 9 | 10 | def run_cmd(cmd = [str]): 11 | result = subprocess.run(cmd, capture_output=True, text=True) 12 | print(result.stderr) 13 | print(result.stdout) 14 | if result.returncode != 0: 15 | print(f"Command failed with error: {result.stderr}") 16 | exit(1) 17 | 18 | return result.stdout 19 | 20 | def run_rv_cmd(cmd = str, args = [str]): 21 | print(f">> Running rv {cmd}") 22 | command = ["rv", cmd, "-vvv"] + args 23 | return run_cmd(command) 24 | 25 | 26 | def run_r_script(script = str): 27 | print(f">> Running R script: {script}") 28 | command = ["Rscript", "-e", script] 29 | return run_cmd(command) 30 | 31 | def add_repo(file_path, repo = str): 32 | with open(file_path, "r") as f: 33 | lines = f.readlines() 34 | 35 | new_lines = [] 36 | in_repos = False 37 | repo_inserted = False 38 | 39 | for i, line in enumerate(lines): 40 | stripped = line.strip() 41 | 42 | if not in_repos and stripped.startswith("repositories") and "[" in stripped: 43 | in_repos = True 44 | new_lines.append(line) 45 | continue 46 | 47 | if in_repos and not repo_inserted: 48 | if stripped.startswith("]"): 49 | new_lines.append(f" {repo},\n") 50 | new_lines.append(line) 51 | repo_inserted = True 52 | in_repos = False 53 | continue 54 | else: 55 | # Insert the new repo before the first existing entry 56 | new_lines.append(f" {repo},\n") 57 | new_lines.append(line) 58 | repo_inserted = True 59 | continue 60 | 61 | new_lines.append(line) 62 | 63 | with open(file_path, "w") as f: 64 | f.writelines(new_lines) 65 | 66 | def check_r_profile(): 67 | if f"{INIT_FOLDER}/rv/library" not in run_r_script(".libPaths()"): 68 | print(f".libPaths not set correctly upon init") 69 | exit(1) 70 | 71 | if "rv-test-repo/repo2" not in run_r_script("getOption('repos')"): 72 | print(f"repos not set correctly upon init") 73 | exit(1) 74 | 75 | 76 | 77 | def run_test(): 78 | os.environ["PATH"] = f"{os.path.abspath('./target/release')}:{os.environ.get('PATH', '')}" 79 | run_rv_cmd("init", [INIT_FOLDER, "--no-repositories", "--force"]) 80 | original_dir = os.getcwd() 81 | os.chdir(INIT_FOLDER) 82 | 83 | 84 | try: 85 | add_repo(CONFIG_FILE, RV_REPO_2) 86 | run_r_script("source('.Rprofile')") 87 | check_r_profile() 88 | run_r_script(".rv$add('rv.git.pkgA', dry_run=TRUE)") 89 | summary = run_rv_cmd("summary", []) 90 | if "Installed: 0/0" not in summary: 91 | print("rv add --dry-run effected the config") 92 | 93 | run_rv_cmd("add", ["rv.git.pkgA", "--no-sync"]) 94 | summary = run_rv_cmd("summary", []) 95 | if "Installed: 0/1" not in summary: 96 | print(f"rv add --no-sync did not behave as expected") 97 | 98 | run_rv_cmd("add", ["rv.git.pkgA"]) 99 | add_repo(CONFIG_FILE, RV_REPO_1) 100 | res = run_rv_cmd("sync", []) 101 | if "Nothing to do" not in res: 102 | print("Adding repo caused re-sync") 103 | res = run_rv_cmd("upgrade", []) 104 | if "- rv.git.pkgA" not in res or "+ rv.git.pkgA (0.0.5" not in res or "from https://a2-ai.github.io/rv-test-repo/repo1)" not in res: 105 | print("Upgrade did not behave as expected") 106 | finally: 107 | os.chdir(original_dir) 108 | 109 | if __name__ == "__main__": 110 | exit(run_test()) -------------------------------------------------------------------------------- /.github/scripts/integration.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import subprocess 4 | import shutil 5 | 6 | PARENT_FOLDER = "example_projects" 7 | SKIP_PLAN_CHECK = ["no-lockfile", "url", "custom-lib-path"] 8 | 9 | def run_cmd(cmd, path, json = False): 10 | print(f">> Running rv {cmd}") 11 | command = ["./target/release/rv", "--config-file", path, "-vvv"] + (["--json"] if json else []) + [cmd] 12 | result = subprocess.run(command, capture_output=True, text=True) 13 | if not json: 14 | print(result.stdout) 15 | print(result.stderr) 16 | 17 | # Check for errors 18 | if result.returncode != 0: 19 | print(f"Command failed with error: {result.stderr}") 20 | exit(1) 21 | 22 | return result.stdout 23 | 24 | 25 | def run_examples(): 26 | items = os.listdir(PARENT_FOLDER) 27 | for subfolder in items: 28 | # This one needs lots of system deps, skipping in CI 29 | if subfolder == "big": 30 | continue 31 | subfolder_path = os.path.join(PARENT_FOLDER, subfolder, "rproject.toml") 32 | print(f"===== Processing example: {subfolder_path} =====") 33 | 34 | # The git packages depend on each other but we don't want rv to use the cache for them 35 | if "git" in subfolder_path: 36 | out = run_cmd("cache", subfolder_path, True) 37 | if out: 38 | cache_data = json.loads(out) 39 | for obj in cache_data.get("git", []): 40 | print(f"Clearing cache: {obj}") 41 | shutil.rmtree(obj["source_path"], ignore_errors=True) 42 | else: 43 | print("Cache command didn't return anything") 44 | 45 | run_cmd("sync", subfolder_path) 46 | plan_result = run_cmd("plan", subfolder_path) 47 | if "Nothing to do" not in plan_result and not any([True for s in SKIP_PLAN_CHECK if s in subfolder_path]): 48 | print(f"Plan after sync has changes planned for {subfolder}") 49 | return 1 50 | library_path = run_cmd("library", subfolder_path) 51 | folder_count = len(os.listdir(library_path.strip())) 52 | 53 | if folder_count == 0: 54 | print(f"No folders found in library for {subfolder}") 55 | return 1 56 | 57 | return 0 58 | 59 | if __name__ == "__main__": 60 | exit(run_examples()) -------------------------------------------------------------------------------- /.github/scripts/tag_check.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import subprocess 4 | 5 | 6 | 7 | def verify_tag(git_tag): 8 | result = subprocess.run( 9 | ["cargo", "run", "--features=cli", "--release", "--", "--version"], 10 | capture_output=True, 11 | text=True, 12 | check=True, 13 | ) 14 | 15 | version = f"v{result.stdout.replace('rv', '').strip()}" 16 | if git_tag != version: 17 | print(f"Different version compared to tag: tag={git_tag} cli={version}") 18 | return 1 19 | 20 | return 0 21 | 22 | 23 | if __name__ == "__main__": 24 | exit(verify_tag(sys.argv[1])) -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - "docs/**" 8 | - "README.md" 9 | pull_request: 10 | paths-ignore: 11 | - "docs/**" 12 | - "README.md" 13 | 14 | jobs: 15 | test: 16 | name: test 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | build: [linux, macos, windows] 21 | include: 22 | - build: linux 23 | os: ubuntu-22.04 24 | rust: stable 25 | - build: macos 26 | os: macOS-latest 27 | rust: stable 28 | - build: windows 29 | os: windows-2022 30 | rust: stable 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Install Rust 34 | uses: dtolnay/rust-toolchain@master 35 | with: 36 | toolchain: ${{ matrix.rust }} 37 | 38 | - name: Build System Info 39 | run: rustc --version 40 | 41 | - name: check with all features enabled 42 | run: | 43 | cargo check 44 | cargo check --all-features 45 | 46 | - name: run tests 47 | run: cargo test --all-features 48 | 49 | integration: 50 | name: integration-test 51 | timeout-minutes: 30 52 | runs-on: ${{ matrix.os }} 53 | strategy: 54 | matrix: 55 | build: [linux, macos, windows] 56 | include: 57 | - build: linux 58 | os: ubuntu-22.04 59 | rust: stable 60 | - build: macos 61 | os: macOS-latest 62 | rust: stable 63 | - build: windows 64 | os: windows-2022 65 | rust: stable 66 | steps: 67 | - uses: actions/checkout@v4 68 | 69 | - name: Set up SSH with repository-specific key 70 | uses: webfactory/ssh-agent@v0.8.0 71 | with: 72 | # ssh key that can only be used to get the repo in example_projects/private-git-dep 73 | ssh-private-key: ${{ secrets.INTERNAL_SSH_KEY }} 74 | 75 | - name: Add GitHub to known_hosts 76 | run: | 77 | ssh-keyscan github.com >> ~/.ssh/known_hosts 78 | 79 | - name: Install Rust 80 | uses: dtolnay/rust-toolchain@master 81 | with: 82 | toolchain: ${{ matrix.rust }} 83 | - name: Build System Info 84 | run: rustc --version 85 | - uses: r-lib/actions/setup-r@v2 86 | with: 87 | r-version: "4.4.2" 88 | - name: Check R on Windows 89 | if: runner.os == 'Windows' 90 | run: | 91 | echo "C:\R\bin" >> $env:GITHUB_PATH 92 | Get-ChildItem -Path "C:\R\bin" -Force 93 | R.exe --version 94 | - name: Make sure libcurl is installed 95 | if: runner.os == 'Linux' 96 | run: | 97 | sudo apt-get update 98 | sudo apt-get install -y libcurl4-openssl-dev 99 | - name: build binary 100 | run: | 101 | cargo build --all-features --release 102 | - name: run rv sync on all example projects 103 | run: python .github/scripts/integration.py 104 | 105 | e2e: 106 | name: e2e-test 107 | timeout-minutes: 30 108 | runs-on: ubuntu-22.04 109 | steps: 110 | - uses: actions/checkout@v4 111 | 112 | # - name: Set up SSH with repository-specific key 113 | # uses: webfactory/ssh-agent@v0.8.0 114 | # with: 115 | # ssh-private-key: ${{ secrets.INTERNAL_SSH_KEY }} 116 | 117 | # - name: Add GitHub to known_hosts 118 | # run: | 119 | # ssh-keyscan github.com >> ~/.ssh/known_hosts 120 | 121 | - name: Install Rust 122 | uses: dtolnay/rust-toolchain@master 123 | with: 124 | toolchain: stable 125 | 126 | - name: Build System Info 127 | run: rustc --version 128 | 129 | - uses: r-lib/actions/setup-r@v2 130 | with: 131 | r-version: "4.4.2" 132 | 133 | - name: build binary 134 | run: | 135 | cargo build --all-features --release 136 | 137 | - name: run rv e2e tests 138 | run: python .github/scripts/e2e.py 139 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ["v*.*.*"] 6 | 7 | env: 8 | # Cross-compilation for aarch64 requires a different linker 9 | CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | Release-Build: 16 | runs-on: ${{ matrix.os }} 17 | permissions: 18 | contents: read 19 | attestations: write 20 | id-token: write 21 | strategy: 22 | matrix: 23 | target: 24 | - x86_64-unknown-linux-gnu 25 | - aarch64-unknown-linux-gnu 26 | - x86_64-pc-windows-msvc 27 | - x86_64-apple-darwin 28 | - aarch64-apple-darwin 29 | rustup_toolchain: [stable] 30 | include: 31 | - os: windows-2022 32 | target: x86_64-pc-windows-msvc 33 | - os: ubuntu-22.04 34 | target: x86_64-unknown-linux-gnu 35 | - os: ubuntu-22.04 36 | target: aarch64-unknown-linux-gnu 37 | - os: macos-13 38 | target: x86_64-apple-darwin 39 | - os: macos-14 40 | target: aarch64-apple-darwin 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | 45 | - name: Install Rust 46 | uses: dtolnay/rust-toolchain@stable 47 | with: 48 | toolchain: ${{ matrix.rustup_toolchain }} 49 | 50 | - name: Install Rust crosscompile tools 51 | if: ${{ contains(matrix.target, 'aarch64-unknown-linux-gnu') }} 52 | run: | 53 | sudo apt-get update -y 54 | sudo apt-get install -y make g++ libssl-dev gcc-aarch64-linux-gnu 55 | rustup target add aarch64-unknown-linux-gnu 56 | 57 | - name: Cargo build 58 | run: cargo build --features=cli --release --target ${{ matrix.target }} 59 | 60 | - name: Check that rv version matches the tag 61 | run: python .github/scripts/tag_check.py "${{ github.ref_name }}" 62 | 63 | - name: Archive (UNIX) 64 | run: | 65 | mkdir -p artifacts 66 | cp -av target/${{ matrix.target }}/release/rv . 67 | tar -czf ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }}.tar.gz rv 68 | if: ${{ ! startsWith(matrix.os, 'windows') }} 69 | 70 | - name: Archive (Windows) 71 | run: | 72 | mkdir -p artifacts 73 | cp target/${{ matrix.target }}/release/rv.exe . 74 | Compress-Archive rv.exe ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }}.zip 75 | if: ${{ startsWith(matrix.os, 'windows') }} 76 | 77 | - name: Attest Build Provenance 78 | uses: actions/attest-build-provenance@v1 79 | continue-on-error: true 80 | with: 81 | subject-path: ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }}.* 82 | 83 | - uses: actions/upload-artifact@v4 84 | with: 85 | name: ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }} 86 | path: ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }}.* 87 | if-no-files-found: error 88 | retention-days: 7 89 | 90 | Release: 91 | needs: [Release-Build] 92 | runs-on: ubuntu-22.04 93 | permissions: 94 | contents: write 95 | 96 | steps: 97 | - name: Ensure artifacts dir exists 98 | run: mkdir -p artifacts 99 | 100 | - name: Download Artifact 101 | uses: actions/download-artifact@v4 102 | with: 103 | path: artifacts 104 | merge-multiple: true 105 | 106 | - name: Release 107 | uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 108 | with: 109 | name: ${{ github.ref_name }} 110 | tag_name: ${{ github.ref_name }} 111 | generate_release_notes: true 112 | fail_on_unmatched_files: true 113 | body: | 114 | Welcome to this new release of rv ${{ github.ref_name }}! 115 | 116 | All artifacts are signed with this repos identity using Sigstore. 117 | You can verify the signatures using the `GitHub` CLI. 118 | 119 | ```shell 120 | gh attestation verify --owner ${{ github.repository_owner }} 121 | ``` 122 | token: ${{ secrets.GITHUB_TOKEN }} 123 | prerelease: ${{ contains(github.ref, '-pre') }} 124 | files: artifacts/* 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | out 4 | src/tests/RCMD/ 5 | example_projects/*/rv 6 | example_projects/test 7 | example_projects/custom-lib-path/libs 8 | example_projects/*/rv.lock 9 | !example_projects/archive/rv.lock 10 | from*/ 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": [ 3 | "cli" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rv" 3 | version = "0.7.1" 4 | edition = "2024" 5 | license = "MIT" 6 | 7 | [dependencies] 8 | toml = "0.8" 9 | serde_json = "1" 10 | url = { version = "2", features = ["serde"] } 11 | # To write the lockfile in the way we want + add/remove things from rproject.toml 12 | toml_edit = "0.22.22" 13 | serde = { version = "1", features = ["derive"] } 14 | # There's a regex to grab the R version from a string and some to parse package files 15 | regex = "1" 16 | # To find the cache directory for each OS 17 | etcetera = "0.10.0" 18 | # Sets some spec like data in the cache dir 19 | cachedir = "0.3" 20 | # To get the OS name, version etc 21 | os_info = "3.9.1" 22 | # We use bincode to serialize package databases to disk 23 | bincode = "2" 24 | # Error handling 25 | thiserror = "2" 26 | fs-err = "3" 27 | # FS things 28 | walkdir = "2" 29 | tempfile = "3" 30 | reflink-copy = "0.1" 31 | filetime = "0.2.25" 32 | # Handling tarballs from repositories or direct url sources 33 | flate2 = "1" 34 | tar = "0.4" 35 | zip = "3" 36 | # HTTP requests 37 | ureq = { version = "3", features = ["platform-verifier", "json"] } 38 | sha2 = "0.10" 39 | # For rv sync 40 | crossbeam = "0.8.4" 41 | num_cpus = "1.16.0" 42 | # some of the progress bars happen in the library 43 | indicatif = "0.17.11" 44 | # To pipe stdout and stderr to a single output for R compilation 45 | os_pipe = "1" 46 | log = "0.4" 47 | 48 | clap = { version = "4", features = ["derive"], optional = true } 49 | clap-verbosity-flag = { version = "3", optional = true } 50 | rayon = { version = "1", optional = true } 51 | anyhow = { version = "1", optional = true } 52 | env_logger = { version = "0.11", optional = true } 53 | jiff = { version = "0.2", optional = true } 54 | 55 | 56 | [features] 57 | cli = [ 58 | "dep:clap", 59 | "dep:rayon", 60 | "dep:anyhow", 61 | "dep:clap-verbosity-flag", 62 | "dep:env_logger", 63 | "dep:jiff", 64 | ] 65 | 66 | [dev-dependencies] 67 | insta = "1" 68 | mockito = "1" 69 | 70 | [profile.release] 71 | codegen-units = 1 72 | lto = true 73 | 74 | 75 | [[bin]] 76 | name = "rv" 77 | required-features = ["cli"] 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 A2-Ai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rv 2 | 3 | `rv` is a new way to manage and install your R packages in a reproducible, fast, and declarative way. 4 | 5 | > [!WARNING] 6 | > `rv` is still in development and may not be fully documented. For additional information, issues, or feature requests, please create an issue or contact us directly. 7 | 8 | ## How it works 9 | 10 | `rv` has several top level commands to provide the user with as much flexibility as possible. The two primary commands are: 11 | ``` 12 | rv plan # detail what will occur if sync is run 13 | rv sync # synchronize the library, config file, and lock file 14 | ``` 15 | 16 | The subsequent actions of these commands are controlled by a configuration file that specifies a desired project state by specifying the R version, repositories, and dependencies the project uses. Additionally, specific package and repository level customizations can be specified. 17 | 18 | For example, a simple configuration file: 19 | ```toml 20 | [project] 21 | name = "my first rv project" 22 | r_version = "4.4" 23 | 24 | # any repositories, order matters 25 | repositories = [ 26 | { alias = "PPM", url = "https://packagemanager.posit.co/cran/latest" }, 27 | ] 28 | 29 | # top level packages to install 30 | dependencies = [ 31 | "dplyr", 32 | { name = "ggplot2", install_suggestions = true} 33 | ] 34 | ``` 35 | 36 | Running `rv sync` will synchronize the library, lock file, and configuration file by installing `dplyr`, `ggplot2`, any dependencies those packages require, and the suggested packages for `ggplot2`. Running `rv plan` will give you a preview of what `rv sync` will do. 37 | 38 | Additional example projects with more configurations can be found in the [example_projects](example_projects) directory of this repository. 39 | 40 | ## Installation 41 | 42 | See the [installation documentation](docs/installation.md) for information on how to install `rv`. 43 | 44 | ## Usage 45 | 46 | See the [usage documentation](docs/usage.md) for information on how to use `rv` and how to [configure](docs/config.md) it with 47 | the `rproject.toml` file. 48 | 49 | ## Contributing 50 | 51 | ### Getting started 52 | 53 | To get started with the development of `rv`, you'll need: 54 | 55 | - [Rust](https://rustup.rs/) 56 | - and optionally [Just](https://github.com/casey/just) 57 | 58 | After installing Rust, you can build the project by running: 59 | 60 | ```bash 61 | just run 62 | // or 63 | cargo run --features=cli --release -- ... 64 | ``` 65 | 66 | e.g. `just run sync` or `just run add --dry-run`. 67 | 68 | If you'd like to install the current version of the project as a binary, you can run: 69 | 70 | ```bash 71 | just install 72 | // or 73 | cargo install --path . --features cli 74 | ``` 75 | 76 | ### Unit testing 77 | 78 | Run the unit tests with: 79 | 80 | ```bash 81 | just test 82 | // or 83 | cargo test --features=cli 84 | ``` 85 | 86 | ### Snapshot testing 87 | 88 | Snapshots require R version 4.4.x. 89 | -------------------------------------------------------------------------------- /docs/config.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | `rv` will read a `rproject.toml` in the current directory, or you can run `rv` from another directory by setting the `--config-file` argument. 4 | 5 | Here's a snippet detailing every field in that configuration file. 6 | 7 | 8 | ```toml 9 | # If this is set to false, the lockfile won't be used for resolution. Defaults to true 10 | use_lockfile = true 11 | # By default the library will be created in the project directory in the `library` folder, and then namespaced 12 | # by R version as well as arch 13 | # You can however set it to any path you want and the packages will be installed directly inside that folder, without 14 | # namespacing. `rv` will never consider a package as installed if that option is set since we can't know how it was 15 | # installed. Useful if you want a common folder for your projects where you can do .libPaths(..) on. 16 | # Defaults to unset 17 | library = "" 18 | 19 | [project] 20 | # Which version is R is required. If we can't that find version somewhere in the system, this will error 21 | r_version = "4.4.1" 22 | 23 | # A list of repositories to fetch packages from. Order matters: we will try to get a package from them in order. 24 | # The alias is only used in this file if you want to specifically require a dependency to come from a certain repository. 25 | repositories = [ 26 | { alias = "cran", url = "https://cran.r-project.org"}, 27 | { alias = "prism", url = "https://prism.dev.a2-ai.cloud/rpkgs/stratus/2025-04-26"}, 28 | ] 29 | 30 | # The main element of the file! This is where you specify your dependencies, as well as some options 31 | dependencies = [ 32 | # A simple string will try to find a package from the repositories in order 33 | "dplyr", 34 | # You can specify a package needs to come from a specific repo by setting the `repository` field to one of the aliases 35 | # defined in the `repositories` array. 36 | { name = "some-package", repository = "prism"}, 37 | # If you need to specify an option, you need to switch to the dict form of a dependency. 38 | # Options for repositories package all default to `false` and are: 39 | # - install_suggestions = true: install the suggested packages or not 40 | # - force_source = true: only get that package from source and not use binary 41 | # - dependencies_only = true: install only the package dependencies but not the package itself 42 | { name = "some-package", install_suggestions = true }, 43 | # You can also install local dependencies if you specify a `path`. 44 | # Options available are `install_suggestions` and `dependencies_only` 45 | { name = "some-package", path = "../some"}, 46 | # The local path can point to a directory or to an archive 47 | { name = "some-package", path = "../some.tar.gz"}, 48 | # You can also use git dependencies. Set the `git` field to the git repository url as well as one of the 49 | # `branch`/`tag`/`commit`. 50 | # This requires to have the `git` CLI available. 51 | # Options available are `install_suggestions` and `dependencies_only` 52 | { name = "some-package", git = "https://github.com/A2-ai/scicalc", tag = "v0.1.1"}, 53 | { name = "some-package", git = "https://github.com/A2-ai/scicalc", commit = "bc50e550e432c3c620714f30dd59115801f89995"}, 54 | { name = "some-package", git = "https://github.com/A2-ai/scicalc", branch = "main"}, 55 | # Lastly, you can point to arbitrary URLs 56 | # Options available are `install_suggestions` and `dependencies_only` 57 | {name = "dplyr", url = "https://cran.r-project.org/src/contrib/Archive/dplyr/dplyr_1.1.3.tar.gz"}, 58 | ] 59 | 60 | # By default, we will always follow the remotes defined in a DESCRIPTION file 61 | # It is possible to override this behaviour by setting the package name in that array if 62 | # the following conditions are met: 63 | # 1. the package has a version requirement 64 | # 2. we can find a package matching that version requirement in a repository 65 | # 66 | # If a package doesn't list a version requirement in the DESCRIPTION file, we will ALWAYS 67 | # install from the remote. 68 | prefer_repositories_for = [] 69 | 70 | # The fields below are reserved and not really used for anything right now 71 | name = "project_name" 72 | description = "" 73 | authors = [{name = "Bob", email="hello@acme.org", maintainer = true}] 74 | license = "MIT" 75 | keywords = [] 76 | suggests = [] 77 | dev_dependencies = [] 78 | 79 | 80 | [project.urls] 81 | homepage = "" 82 | issues = "" 83 | 84 | ``` -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Mac 4 | 5 | ### Homebrew (mac) 6 | 7 | ``` 8 | brew tap a2-ai/homebrew-tap 9 | brew install rv 10 | ``` 11 | 12 | ## For Unix-like systems (Linux, macOS) 13 | 14 | ### Download the latest release 15 | 16 | You can use the script or download the archive from the [GitHub releases page](https://github.com/a2-ai/rv/releases/latest). 17 | 18 | ```shell 19 | curl -sSL https://raw.githubusercontent.com/A2-ai/rv/refs/heads/main/scripts/install.sh | bash 20 | 21 | rv --version 22 | ``` 23 | 24 | ## For Windows 25 | 26 | ### Download the latest release 27 | 28 | For now, you can download the latest `x86_64-pc-windows-msvc` zip archive from the [GitHub releases page](https://github.com/a2-ai/rv/releases/latest) and extract it to a directory of your choice. 29 | 30 | ### Add the `rv` binary to your PATH 31 | 32 | ```powershell 33 | $env:Path += ";C:\path\to\rv" 34 | 35 | .\rv.exe --version 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | 2 | # Usage - Commands, Flags, and Terminology to know 3 | 4 | > [!TIP] 5 | > Before diving into the commands and flags, a couple terms are used interchangably throughout the documentation, code base, and configuration file: 6 | > * **Dependencies**: Are packages the project *depends* on 7 | > * **Sync**: Is installing dependencies to *synchronize* the package library, config file, and lock file. 8 | 9 | `rv` has many top level commands to help you easily setup your projects, install/sync packages, and see the status of your project. For a full list of commands, please run `rv --help`. 10 | 11 | ## Starting a new `rv` project 12 | 13 | ### From scratch 14 | `rv init` will initialize a new or existing project by: 15 | 1. Setting up the project infrastructure, including the project library and an activation script to ensure the rv library is used for this project 16 | 2. Create a configuration file which is populated with the R version and repositories 17 | 18 | You can customize this configuration file using the following flags: 19 | * `--r-version`: The R version is set to be the version found on the path by default. This flag allows you to set any custom version 20 | > [!NOTE] 21 | > For RStudio/Positron users, the R version on the path does NOT always match the version set for the session. Please use this flag to ensure correct R version is used for your project 22 | * `--no-repositories`: The repositories are set as what is found in the current R session. This flag sets the repositories field in the configuration file to blank 23 | * `--add`: The dependency field is blank by default. This flag can be used to add dependencies you know will be needed to the project directly to the config 24 | > [!NOTE] 25 | > `rv init` will not automatically sync your project 26 | 27 | For interactive R sessions, we recommend restarting R after initializing your project to ensure your library paths are set properly 28 | 29 | ### From a renv project 30 | `rv migrate renv` will initialize an existing renv project by migrating your renv.lock file to a rv configuration file. 31 | 32 | We cannot guarantee `rv` will migrate your renv project in its entirety, but any dependencies not fully migrated will be logged. 33 | 34 | Some common reasons a dependency may not be able to be migrated: 35 | * It could not be found in any of the repositories listed in the renv.lock. 36 | > [!TIP] 37 | > Determine the repository the dependency was installed from and add both to the configuration file 38 | * The correct version could not be found in any of the repositories listed in the renv.lock 39 | > [!TIP] 40 | > * If the exact version is required and can be found in a different repository, add both the dependency and repository to the config 41 | > * If the exact version is required, use the url dependency format to directly access the archive (i.e. {name = "dplyr", url = "https://cran.r-project.org/src/contrib/Archive/dplyr_1.1.3.tar.gz"}) 42 | > * If the exact version is not required, add the dependency to the config 43 | 44 | For interactive R sessions, we recommend restarting R after initializing your project to ensure your library paths are set properly 45 | 46 | ## Installing packages 47 | `rv sync` is used to synchronize the lock file, configuration file, and library of a project. So if a new package is added to your configuration file, `rv sync` will install the package and its dependencies. 48 | 49 | For quick editing, you can use `rv add ...` which will add these packages to the dependencies section of the config file and sync. 50 | 51 | Additionally, you can use the following flags: 52 | * `--no-sync` will add the listed packages to the config but will NOT sync 53 | * `--dry-run` will not make any changes and only report what would happen if you were to install those packages 54 | 55 | For more complex edits, including specific sources and other configuration, you can directly edit the configuration file and re-run `rv sync`. 56 | 57 | ## Upgrading packages 58 | `rv` will default to installing packages from the source they were originally installed from if the repository is still listed in the configuration file. 59 | 60 | `rv upgrade` will ignore the lockfile, re-resolve, and install any changed packages. This does not necessarily upgrade to the latest version available across repositories, simply your project state will 61 | be upgraded as if there was no lockfile present. 62 | 63 | If you'd like to see what will occur when you were to upgrade, run `rv upgrade --dry-run` or `rv plan --upgrade`. -------------------------------------------------------------------------------- /dummy-pkg/.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^dummy.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | -------------------------------------------------------------------------------- /dummy-pkg/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: dummy 2 | Title: Dummy package 3 | Version: 0.0.0.9001 4 | Authors@R: 5 | person("Wes", "Cummings", , "wes@a2-ai.com", role = c("aut", "cre")) 6 | Description: Test package 7 | Imports: R6 8 | License: MIT + file LICENSE 9 | Encoding: UTF-8 10 | Roxygen: list(markdown = TRUE) 11 | RoxygenNote: 7.3.2 12 | -------------------------------------------------------------------------------- /dummy-pkg/LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2025 2 | COPYRIGHT HOLDER: dummy authors 3 | -------------------------------------------------------------------------------- /dummy-pkg/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 dummy authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dummy-pkg/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(what_version_am_i) 4 | -------------------------------------------------------------------------------- /dummy-pkg/R/lib.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | what_version_am_i <- function() { 3 | print("dummy - v3") 4 | } -------------------------------------------------------------------------------- /dummy-pkg/dummy.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /dummy.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A2-ai/rv/627b16ddbc6e337ebe03c4514225c6f9e7ae4bff/dummy.tar.gz -------------------------------------------------------------------------------- /example_projects/archive/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | # NOTE: do not update the lockfile from this project since we're testing whether we can install a non-current package version 3 | name = "archive" 4 | r_version = "4.4" 5 | repositories = [ 6 | { alias = "CRAN", url = "https://cran.r-project.org" }, 7 | ] 8 | dependencies = [ 9 | "R6", 10 | ] -------------------------------------------------------------------------------- /example_projects/archive/rv.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by rv. 2 | # It is not intended for manual editing. 3 | version = 2 4 | r_version = "4.4" 5 | 6 | [[packages]] 7 | name = "R6" 8 | version = "2.5.0" 9 | source = { repository = "https://cran.r-project.org" } 10 | force_source = false 11 | dependencies = [] 12 | -------------------------------------------------------------------------------- /example_projects/custom-lib-path/rproject.toml: -------------------------------------------------------------------------------- 1 | library = "libs" 2 | 3 | [project] 4 | name = "simple" 5 | r_version = "4.4" 6 | repositories = [ 7 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 8 | ] 9 | dependencies = [ 10 | "dplyr", 11 | ] -------------------------------------------------------------------------------- /example_projects/cyclic-suggests/README.md: -------------------------------------------------------------------------------- 1 | README 2 | =============== 3 | 4 | dplyr has a suggested dep of Lahman, while Lahman imports dplyr. As such, 5 | dplyr requests lahman to be installed, by dplyr must be installed before Lahman can. 6 | -------------------------------------------------------------------------------- /example_projects/cyclic-suggests/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "simple" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | { name = "markdown", install_suggestions = true } 9 | ] 10 | -------------------------------------------------------------------------------- /example_projects/git-dep-branch/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "git-dep" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | { name = "rv.git.pkgA", git = "https://github.com/A2-ai/rv.git.pkgA", branch = "main", install_suggestions = true}, 9 | ] 10 | -------------------------------------------------------------------------------- /example_projects/git-dep-tag/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "git-dep" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | # This has a remote on github::a2-ai/rv.git.pkgA 9 | { name = "rv.git.pkgB", git = "https://github.com/A2-ai/rv.git.pkgB/", tag ="v1.0", install_suggestions = true}, 10 | ] 11 | -------------------------------------------------------------------------------- /example_projects/local-deps/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "simple" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | { name = "dummy", path = "../../dummy-pkg/" } 9 | ] -------------------------------------------------------------------------------- /example_projects/local-tarball-dep/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "simple" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | { name = "dummy", path = "../../dummy.tar.gz" } 9 | ] -------------------------------------------------------------------------------- /example_projects/multi-repo/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "multi-repo" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"}, 6 | { alias = "a2-ai", url = "https://a2-ai.github.io/rv-test-snapshot/2024-12-22/" } 7 | ] 8 | dependencies = [ 9 | "pkgpub" 10 | ] 11 | -------------------------------------------------------------------------------- /example_projects/no-lockfile/rproject.toml: -------------------------------------------------------------------------------- 1 | use_lockfile = false 2 | 3 | [project] 4 | name = "simple" 5 | r_version = "4.4" 6 | repositories = [ 7 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 8 | ] 9 | dependencies = [ 10 | "R6", 11 | ] -------------------------------------------------------------------------------- /example_projects/package-upgrade/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | # Note: do not update the lockfile from this project since we're testing whether an upgrade 3 | # (eg with the new- repos) will pick up all the news dependencies 4 | name = "package-upgrade" 5 | r_version = "4.4" 6 | 7 | repositories = [ 8 | { alias = "gh-pkg-mirror", url = "https://a2-ai.github.io/gh-pkg-mirror/2024-02-22" }, 9 | { alias = "RSPM", url = "https://packagemanager.posit.co/cran/2024-02-22" }, 10 | { alias = "new-mirror", url = "https://a2-ai.github.io/gh-pkg-mirror/2024-12-04" }, 11 | { alias = "new-rspm", url = "https://packagemanager.posit.co/cran/2024-12-04" }, 12 | ] 13 | 14 | dependencies = [ 15 | {name = "pmplots", repository = "new-mirror"}, 16 | "pmtables", 17 | "bbr", 18 | {name = "ggplot2", repository = "new-rspm"}, 19 | ] -------------------------------------------------------------------------------- /example_projects/private-git-dep/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "private-git-dep" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | # This one has a private git remote for a dep. 9 | # Only members of the a2-ai org will be able to install this example project 10 | { name = "rdstarlight", git = "git@github.com:A2-ai/rdstarlight.git", commit ="869aa1020fed41ba87ba54290105392ea805fa75"}, 11 | ] 12 | -------------------------------------------------------------------------------- /example_projects/project-upgrade/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | # Note: do not update the lockfile from this project since we're testing whether an upgrade 3 | # url is updated by 10 months to upgrade the version/source of the packages 4 | name = "project-upgrade" 5 | r_version = "4.4" 6 | repositories = [ 7 | {alias = "PPM", url = "https://packagemanager.posit.co/cran/2025-04-12"}, 8 | {alias = "PPM-old", url = "https://packagemanager.posit.co/cran/2025-04-01"} 9 | ] 10 | 11 | dependencies = [ 12 | "ggplot2" 13 | ] -------------------------------------------------------------------------------- /example_projects/project-upgrade/rv.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by rv. 2 | # It is not intended for manual editing. 3 | version = 1 4 | r_version = "4.4" 5 | 6 | [[packages]] 7 | name = "R6" 8 | version = "2.6.1" 9 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 10 | force_source = false 11 | dependencies = [] 12 | 13 | [[packages]] 14 | name = "RColorBrewer" 15 | version = "1.1-3" 16 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 17 | force_source = false 18 | dependencies = [] 19 | 20 | [[packages]] 21 | name = "cli" 22 | version = "3.6.4" 23 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 24 | force_source = false 25 | dependencies = [] 26 | 27 | [[packages]] 28 | name = "colorspace" 29 | version = "2.1-1" 30 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 31 | force_source = false 32 | dependencies = [] 33 | 34 | [[packages]] 35 | name = "fansi" 36 | version = "1.0.6" 37 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 38 | force_source = false 39 | dependencies = [] 40 | 41 | [[packages]] 42 | name = "farver" 43 | version = "2.1.2" 44 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 45 | force_source = false 46 | dependencies = [] 47 | 48 | [[packages]] 49 | name = "ggplot2" 50 | version = "3.5.1" 51 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 52 | force_source = false 53 | dependencies = [ 54 | "cli", 55 | "glue", 56 | "gtable", 57 | "isoband", 58 | "lifecycle", 59 | "rlang", 60 | "scales", 61 | "tibble", 62 | "vctrs", 63 | "withr", 64 | ] 65 | 66 | [[packages]] 67 | name = "glue" 68 | version = "1.8.0" 69 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 70 | force_source = false 71 | dependencies = [] 72 | 73 | [[packages]] 74 | name = "gtable" 75 | version = "0.3.6" 76 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 77 | force_source = false 78 | dependencies = [ 79 | "cli", 80 | "glue", 81 | "lifecycle", 82 | "rlang", 83 | ] 84 | 85 | [[packages]] 86 | name = "isoband" 87 | version = "0.2.7" 88 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 89 | force_source = false 90 | dependencies = [] 91 | 92 | [[packages]] 93 | name = "labeling" 94 | version = "0.4.3" 95 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 96 | force_source = false 97 | dependencies = [] 98 | 99 | [[packages]] 100 | name = "lifecycle" 101 | version = "1.0.4" 102 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 103 | force_source = false 104 | dependencies = [ 105 | "cli", 106 | "glue", 107 | "rlang", 108 | ] 109 | 110 | [[packages]] 111 | name = "magrittr" 112 | version = "2.0.3" 113 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 114 | force_source = false 115 | dependencies = [] 116 | 117 | [[packages]] 118 | name = "munsell" 119 | version = "0.5.1" 120 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 121 | force_source = false 122 | dependencies = [ 123 | "colorspace", 124 | ] 125 | 126 | [[packages]] 127 | name = "pillar" 128 | version = "1.10.1" 129 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 130 | force_source = false 131 | dependencies = [ 132 | "cli", 133 | "glue", 134 | "lifecycle", 135 | "rlang", 136 | "utf8", 137 | "vctrs", 138 | ] 139 | 140 | [[packages]] 141 | name = "pkgconfig" 142 | version = "2.0.3" 143 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 144 | force_source = false 145 | dependencies = [] 146 | 147 | [[packages]] 148 | name = "rlang" 149 | version = "1.1.5" 150 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 151 | force_source = false 152 | dependencies = [] 153 | 154 | [[packages]] 155 | name = "scales" 156 | version = "1.3.0" 157 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 158 | force_source = false 159 | dependencies = [ 160 | "cli", 161 | "farver", 162 | "glue", 163 | "labeling", 164 | "lifecycle", 165 | "munsell", 166 | "R6", 167 | "RColorBrewer", 168 | "rlang", 169 | "viridisLite", 170 | ] 171 | 172 | [[packages]] 173 | name = "tibble" 174 | version = "3.2.1" 175 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 176 | force_source = false 177 | dependencies = [ 178 | "fansi", 179 | "lifecycle", 180 | "magrittr", 181 | "pillar", 182 | "pkgconfig", 183 | "rlang", 184 | "vctrs", 185 | ] 186 | 187 | [[packages]] 188 | name = "utf8" 189 | version = "1.2.4" 190 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 191 | force_source = false 192 | dependencies = [] 193 | 194 | [[packages]] 195 | name = "vctrs" 196 | version = "0.6.5" 197 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 198 | force_source = false 199 | dependencies = [ 200 | "cli", 201 | "glue", 202 | "lifecycle", 203 | "rlang", 204 | ] 205 | 206 | [[packages]] 207 | name = "viridisLite" 208 | version = "0.4.2" 209 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 210 | force_source = false 211 | dependencies = [] 212 | 213 | [[packages]] 214 | name = "withr" 215 | version = "3.0.2" 216 | source = { repository = "https://packagemanager.posit.co/cran/2025-04-01" } 217 | force_source = false 218 | dependencies = [] 219 | -------------------------------------------------------------------------------- /example_projects/r-universe/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "r-universe" 3 | r_version = "4.4" 4 | 5 | dependencies = [ 6 | "osinfo" 7 | ] 8 | 9 | repositories = [ 10 | {alias = "r-universe", url = "https://a2-ai.r-universe.dev"}, 11 | {alias = "RSPM", url = "https://packagemanager.posit.co/cran/2024-12-04"}, 12 | ] -------------------------------------------------------------------------------- /example_projects/rspm-cran/rproject.toml: -------------------------------------------------------------------------------- 1 | 2 | [project] 3 | name = "rspm" 4 | r_version = "4.4" 5 | 6 | repositories = [ 7 | { alias = "RSPM", url = "https://packagemanager.posit.co/cran/latest/" }, 8 | { alias = "CRAN", url = "https://cran.r-project.org" }, 9 | ] 10 | 11 | dependencies = [ 12 | "R6", 13 | {name = "renv", repository = "CRAN"} 14 | ] -------------------------------------------------------------------------------- /example_projects/simple/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "simple" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | "dplyr", 9 | ] -------------------------------------------------------------------------------- /example_projects/trailing-slash/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "trailing-slash" 3 | r_version = "4.4" 4 | description = "URL's that ended with a slash should not rely on servers to properly parse (i.e. PRISM does not)" 5 | 6 | repositories = [ 7 | { alias = "stratus", url = "https://prism.dev.a2-ai.cloud/rpkgs/stratus/2025-05-09/" }, 8 | ] 9 | 10 | dependencies = [ 11 | "R6" 12 | ] -------------------------------------------------------------------------------- /example_projects/url-dep/rproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "simple" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/", force_source = true} 6 | ] 7 | dependencies = [ 8 | {name = "dplyr", url = "https://cran.r-project.org/src/contrib/Archive/dplyr/dplyr_1.1.3.tar.gz"} 9 | ] 10 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | run *args: 2 | cargo run --features=cli -- {{args}} 3 | 4 | test: 5 | cargo test --features=cli 6 | 7 | install: 8 | cargo install --path . --features=cli -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Ensure target directory exists and cd into it 5 | mkdir -p ~/.local/bin && cd ~/.local/bin 6 | 7 | # Determine OS and Architecture 8 | os=$(uname -s | tr '[:upper:]' '[:lower:]') 9 | arch=$(uname -m) 10 | if [ "$arch" = "arm64" ]; then arch="aarch64"; elif [ "$arch" = "x86_64" ]; then arch="x86_64"; fi 11 | 12 | # Adjust OS string for macOS asset naming convention 13 | if [ "$os" = "darwin" ]; then 14 | os_pattern="apple-darwin" 15 | else 16 | os_pattern="unknown-linux-gnu" 17 | fi 18 | 19 | # Fetch the latest release data from GitHub API and extract the download URL for the matching asset 20 | echo "Fetching download URL for $arch-$os_pattern..." 21 | asset_url=$(curl -s https://api.github.com/repos/a2-ai/rv/releases/latest | grep -o "https://github.com/A2-ai/rv/releases/download/.*$arch-$os_pattern.tar.gz") 22 | 23 | # Check if URL was found 24 | if [ -z "$asset_url" ]; then 25 | echo "Error: Could not find a suitable release asset for your system ($arch-$os_pattern) on GitHub." >&2 26 | echo "Please check available assets at https://github.com/a2-ai/rv/releases/latest" >&2 27 | exit 1 28 | fi 29 | 30 | # Download the asset using curl, extract it, clean up, and make executable 31 | echo "Downloading rv from $asset_url" 32 | curl -L -o rv_latest.tar.gz "$asset_url" && 33 | tar -xzf rv_latest.tar.gz && 34 | rm rv_latest.tar.gz && 35 | chmod +x rv && 36 | echo "rv installed successfully to ~/.local/bin" || 37 | (echo "Installation failed." >&2 && exit 1) 38 | 39 | # Add ~/.local/bin to PATH if not already present 40 | if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then 41 | echo "Adding ~/.local/bin to your PATH..." 42 | if [[ "$SHELL" == *"bash"* ]]; then 43 | printf '\n%s\n' 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc 44 | echo "Please source ~/.bashrc or open a new terminal." 45 | elif [[ "$SHELL" == *"zsh"* ]]; then 46 | printf '\n%s\n' 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc 47 | echo "Please source ~/.zshrc or open a new terminal." 48 | elif [[ "$SHELL" == *"fish"* ]]; then 49 | printf '\n%s\n' 'fish_add_path "$HOME/.local/bin"' >> ~/.config/fish/config.fish 50 | echo "~/.local/bin added to fish path. Changes will apply to new fish shells." 51 | else 52 | echo "Could not detect shell. Please add ~/.local/bin to your PATH manually." 53 | fi 54 | else 55 | echo "~/.local/bin is already in your PATH." 56 | fi 57 | -------------------------------------------------------------------------------- /src/add.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use std::fs; 4 | use toml_edit::{Array, DocumentMut, Formatted, Value}; 5 | 6 | use crate::{Config, config::ConfigLoadError}; 7 | 8 | pub fn read_and_verify_config(config_file: impl AsRef) -> Result { 9 | let config_file = config_file.as_ref(); 10 | let _ = Config::from_file(config_file).map_err(|e| AddError { 11 | path: config_file.into(), 12 | source: Box::new(AddErrorKind::ConfigLoad(e)), 13 | })?; 14 | let config_content = fs::read_to_string(config_file).unwrap(); // Verified config could be loaded above 15 | 16 | Ok(config_content.parse::().unwrap()) // Verify config was valid toml above 17 | } 18 | 19 | pub fn add_packages(config_doc: &mut DocumentMut, packages: Vec) -> Result<(), AddError> { 20 | // get the dependencies array 21 | let config_deps = get_mut_array(config_doc); 22 | 23 | // collect the names of all of the dependencies 24 | let config_dep_names = config_deps 25 | .iter() 26 | .filter_map(|v| match v { 27 | Value::String(s) => Some(s.value().as_str()), 28 | Value::InlineTable(t) => t.get("name").and_then(|v| v.as_str()), 29 | _ => None, 30 | }) 31 | .map(|s| s.to_string()) // Need to allocate so values are not a reference to a mut 32 | .collect::>(); 33 | 34 | // Determine if the dep to add is in the config, if not add it 35 | for d in packages { 36 | if !config_dep_names.contains(&d) { 37 | config_deps.push(Value::String(Formatted::new(d))); 38 | // Couldn't format value before pushing, so adding formatting after its added 39 | if let Some(last) = config_deps.iter_mut().last() { 40 | last.decor_mut().set_prefix("\n "); 41 | } 42 | } 43 | } 44 | 45 | // Set a trailing new line and comma for the last element for proper formatting 46 | config_deps.set_trailing("\n"); 47 | config_deps.set_trailing_comma(true); 48 | 49 | Ok(()) 50 | } 51 | 52 | fn get_mut_array(doc: &mut DocumentMut) -> &mut Array { 53 | // the dependnecies array is behind the project table 54 | let deps = doc 55 | .get_mut("project") 56 | .and_then(|item| item.as_table_mut()) 57 | .unwrap() 58 | .entry("dependencies") 59 | .or_insert_with(|| Array::new().into()) 60 | .as_array_mut() 61 | .unwrap(); 62 | 63 | // remove formatting on the last element as we will re-add 64 | if let Some(last) = deps.iter_mut().last() { 65 | last.decor_mut().set_suffix(""); 66 | } 67 | deps 68 | } 69 | 70 | #[derive(Debug, thiserror::Error)] 71 | #[error("Failed to edit config at `{path}`")] 72 | #[non_exhaustive] 73 | pub struct AddError { 74 | path: Box, 75 | source: Box, 76 | } 77 | 78 | #[derive(Debug, thiserror::Error)] 79 | #[error(transparent)] 80 | pub enum AddErrorKind { 81 | Io(#[from] std::io::Error), 82 | Parse(#[from] toml_edit::TomlError), 83 | ConfigLoad(#[from] ConfigLoadError), 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use crate::{add_packages, read_and_verify_config}; 89 | 90 | #[test] 91 | fn add_remove() { 92 | let config_file = "src/tests/valid_config/all_fields.toml"; 93 | let mut doc = read_and_verify_config(&config_file).unwrap(); 94 | add_packages(&mut doc, vec!["pkg1".to_string(), "pkg2".to_string()]).unwrap(); 95 | insta::assert_snapshot!("add_remove", doc.to_string()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/cache/info.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::path::PathBuf; 3 | 4 | use crate::lockfile::Source; 5 | use crate::{Config, DiskCache, ResolvedDependency, hash_string}; 6 | use serde::Serialize; 7 | 8 | /// Both for git and remote urls 9 | #[derive(Debug, Serialize)] 10 | struct CacheUrlInfo { 11 | url: String, 12 | source_path: PathBuf, 13 | binary_path: PathBuf, 14 | } 15 | impl fmt::Display for CacheUrlInfo { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | write!( 18 | f, 19 | "{}, source: {}, binary: {}", 20 | self.url, 21 | self.source_path.display(), 22 | self.binary_path.display() 23 | ) 24 | } 25 | } 26 | 27 | #[derive(Debug, Serialize)] 28 | struct CacheRepositoryInfo { 29 | alias: String, 30 | url: String, 31 | hash: String, 32 | path: PathBuf, 33 | } 34 | 35 | impl fmt::Display for CacheRepositoryInfo { 36 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 | write!( 38 | f, 39 | "{} ({} -> {}), path: {}", 40 | self.alias, 41 | self.url, 42 | self.hash, 43 | self.path.display() 44 | ) 45 | } 46 | } 47 | 48 | #[derive(Debug, Serialize)] 49 | pub struct CacheInfo { 50 | root: PathBuf, 51 | repositories: Vec, 52 | git: Vec, 53 | urls: Vec, 54 | } 55 | 56 | impl CacheInfo { 57 | pub fn new(config: &Config, cache: &DiskCache, resolved: Vec) -> Self { 58 | let root = cache.root.clone(); 59 | let repositories = config 60 | .repositories() 61 | .iter() 62 | .map(|r| { 63 | let hash = hash_string(r.url()); 64 | CacheRepositoryInfo { 65 | alias: r.alias.clone(), 66 | url: r.url().to_string(), 67 | path: root.join(hash_string(r.url())), 68 | hash, 69 | } 70 | }) 71 | .collect(); 72 | 73 | let mut git_paths = Vec::new(); 74 | let mut url_paths = Vec::new(); 75 | 76 | for d in resolved { 77 | if !d.source.is_git_or_url() { 78 | continue; 79 | } 80 | let paths = cache.get_package_paths(&d.source, None, None); 81 | match d.source { 82 | Source::Git { git, .. } => { 83 | git_paths.push(CacheUrlInfo { 84 | url: git.url().to_string(), 85 | source_path: paths.source, 86 | binary_path: paths.binary, 87 | }); 88 | } 89 | Source::RUniverse { git, .. } => { 90 | git_paths.push(CacheUrlInfo { 91 | url: git.url().to_string(), 92 | source_path: paths.source, 93 | binary_path: paths.binary, 94 | }); 95 | } 96 | Source::Url { url, .. } => { 97 | url_paths.push(CacheUrlInfo { 98 | url: url.as_str().to_string(), 99 | source_path: paths.source, 100 | binary_path: paths.binary, 101 | }); 102 | } 103 | _ => continue, 104 | } 105 | } 106 | 107 | Self { 108 | root, 109 | repositories, 110 | git: git_paths, 111 | urls: url_paths, 112 | } 113 | } 114 | } 115 | 116 | impl fmt::Display for CacheInfo { 117 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 118 | writeln!(f, "{}", self.root.display())?; 119 | for r in &self.repositories { 120 | writeln!(f, "{}", r)?; 121 | } 122 | for r in &self.git { 123 | writeln!(f, "Git: {}", r)?; 124 | } 125 | for r in &self.urls { 126 | writeln!(f, "Url: {}", r)?; 127 | } 128 | 129 | Ok(()) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/cache/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod disk; 2 | mod info; 3 | pub mod utils; 4 | 5 | pub use disk::{DiskCache, InstallationStatus, PackagePaths}; 6 | pub use info::CacheInfo; 7 | -------------------------------------------------------------------------------- /src/cache/utils.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use etcetera::BaseStrategy; 4 | use sha2::{Digest, Sha256}; 5 | 6 | use crate::SystemInfo; 7 | 8 | /// Builds the path for binary in the cache and the library based on system info and R version 9 | /// {R_Version}/{arch}/{codename}/ 10 | pub fn get_current_system_path(system_info: &SystemInfo, r_version: [u32; 2]) -> PathBuf { 11 | let mut path = PathBuf::new().join(format!("{}.{}", r_version[0], r_version[1])); 12 | 13 | if let Some(arch) = system_info.arch() { 14 | path = path.join(arch); 15 | } 16 | if let Some(codename) = system_info.codename() { 17 | path = path.join(codename); 18 | } 19 | 20 | path 21 | } 22 | 23 | /// Look up the env to see if a specific timeout is set, otherwise use the default value 24 | pub fn get_packages_timeout() -> u64 { 25 | if let Ok(v) = std::env::var(crate::consts::PACKAGE_TIMEOUT_ENV_VAR_NAME) { 26 | if let Ok(v2) = v.parse() { 27 | v2 28 | } else { 29 | // If the variable doesn't parse into a valid number, return the default one 30 | crate::consts::PACKAGE_TIMEOUT 31 | } 32 | } else { 33 | crate::consts::PACKAGE_TIMEOUT 34 | } 35 | } 36 | 37 | /// Try to get where the rv cache dir should be 38 | pub fn get_user_cache_dir() -> Option { 39 | etcetera::base_strategy::choose_base_strategy() 40 | .ok() 41 | .map(|dirs| dirs.cache_dir().join("rv")) 42 | } 43 | 44 | /// Equivalent to sha256(input)[:10] 45 | pub fn hash_string(input: &str) -> String { 46 | let mut hasher = Sha256::new(); 47 | hasher.update(input.to_ascii_lowercase().as_bytes()); 48 | let result = format!("{:x}", hasher.finalize()); 49 | result[..10].to_string() 50 | } 51 | -------------------------------------------------------------------------------- /src/cli/commands/migrate.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::Write, 4 | path::{Path, absolute}, 5 | }; 6 | 7 | use anyhow::{Result, anyhow}; 8 | 9 | use crate::{ 10 | DiskCache, RenvLock, Repository, SystemInfo, 11 | cli::context::load_databases, 12 | renv::{ResolvedRenv, UnresolvedRenv}, 13 | }; 14 | 15 | const RENV_CONFIG_TEMPLATE: &str = r#"# this config was migrated from %renv_file% on %time% 16 | [project] 17 | name = "%project_name%" 18 | r_version = "%r_version%" 19 | 20 | repositories = [ 21 | %repositories% 22 | ] 23 | 24 | dependencies = [ 25 | %dependencies% 26 | ] 27 | "#; 28 | 29 | pub fn migrate_renv( 30 | renv_file: impl AsRef, 31 | config_file: impl AsRef, 32 | strict_r_version: bool, 33 | ) -> Result> { 34 | // project name is the parent directory of the renv project 35 | let abs_renv_file = absolute(renv_file.as_ref())?; 36 | let project_name = abs_renv_file 37 | .parent() 38 | .and_then(|p| p.to_str()) 39 | .unwrap_or("renv migrated project"); 40 | 41 | // use the repositories and r version from the renv.lock to determine the repository databases 42 | let renv_lock = RenvLock::parse_renv_lock(&renv_file)?; 43 | let cache = match DiskCache::new(renv_lock.r_version(), SystemInfo::from_os_info()) { 44 | Ok(c) => c, 45 | Err(e) => return Err(anyhow!(e)), 46 | }; 47 | let databases = load_databases(&renv_lock.config_repositories(), &cache)?; 48 | 49 | // resolve the renv.lock file to determine the true source of packages 50 | let (resolved, unresolved) = renv_lock.resolve(&databases); 51 | 52 | // Write config out to the config file specified in the cli, even if config file is outside of the renv.lock project 53 | let r_version = if strict_r_version { 54 | &renv_lock.r_version().original 55 | } else { 56 | let [major, minor] = renv_lock.r_version().major_minor(); 57 | &format!("{major}.{minor}") 58 | }; 59 | 60 | let config = render_config( 61 | &renv_file.as_ref().to_string_lossy(), 62 | project_name, 63 | r_version, 64 | &renv_lock.config_repositories(), 65 | &resolved, 66 | ); 67 | let mut file = File::create(&config_file)?; 68 | file.write_all(config.as_bytes())?; 69 | Ok(unresolved) 70 | } 71 | 72 | fn render_config( 73 | renv_file: &str, 74 | project_name: &str, 75 | r_version: &str, 76 | repositories: &[Repository], 77 | resolved_deps: &[ResolvedRenv], 78 | ) -> String { 79 | let repos = repositories 80 | .iter() 81 | .map(|r| { 82 | format!( 83 | r#" {{ alias = "{}", url = "{}"{}}}"#, 84 | r.alias, 85 | r.url(), 86 | if r.force_source { 87 | r#", force_source = true"#.to_string() 88 | } else { 89 | String::new() 90 | } 91 | ) 92 | }) 93 | .collect::>() 94 | .join(",\n"); 95 | 96 | // print alphabetically to match with plan/sync output 97 | let deps = resolved_deps 98 | .iter() 99 | .map(|d| format!(" {d}")) 100 | .collect::>() 101 | .join(",\n"); 102 | // get time. Try to round to seconds, but if error, leave as unrounded 103 | let time = jiff::Zoned::now(); 104 | // Format the time as just the date (YYYY-MM-DD) 105 | let time = time.date().to_string(); 106 | 107 | RENV_CONFIG_TEMPLATE 108 | .replace("%renv_file%", renv_file) 109 | .replace("%time%", &time.to_string()) 110 | .replace("%project_name%", project_name) 111 | .replace("%r_version%", r_version) 112 | .replace("%repositories%", &repos) 113 | .replace("%dependencies%", &deps) 114 | } 115 | -------------------------------------------------------------------------------- /src/cli/commands/mod.rs: -------------------------------------------------------------------------------- 1 | mod init; 2 | mod migrate; 3 | 4 | pub use init::{find_r_repositories, init, init_structure}; 5 | pub use migrate::migrate_renv; 6 | -------------------------------------------------------------------------------- /src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | mod commands; 2 | mod context; 3 | pub mod utils; 4 | 5 | pub use commands::{find_r_repositories, init, init_structure, migrate_renv}; 6 | pub use context::CliContext; 7 | -------------------------------------------------------------------------------- /src/cli/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn write_err(err: &(dyn std::error::Error + 'static)) -> String { 2 | let mut out = format!("{err}"); 3 | 4 | let mut cause = err.source(); 5 | while let Some(e) = cause { 6 | out += &format!("\nReason: {e}"); 7 | cause = e.source(); 8 | } 9 | 10 | out 11 | } 12 | 13 | #[macro_export] 14 | macro_rules! timeit { 15 | ($msg:expr, $x:expr) => {{ 16 | let start = std::time::Instant::now(); 17 | let res = $x; 18 | let duration = start.elapsed(); 19 | log::info!("{} in {}ms", $msg, duration.as_millis()); 20 | res 21 | }}; 22 | } 23 | 24 | pub use timeit; 25 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | pub const PACKAGE_FILENAME: &str = "PACKAGES"; 2 | pub const DESCRIPTION_FILENAME: &str = "DESCRIPTION"; 3 | pub const SOURCE_PACKAGES_PATH: &str = "/src/contrib/PACKAGES"; 4 | pub const LOCKFILE_NAME: &str = "rv.lock"; 5 | 6 | pub const RV_DIR_NAME: &str = "rv"; 7 | pub const LIBRARY_ROOT_DIR_NAME: &str = "library"; 8 | pub const STAGING_DIR_NAME: &str = "__rv__staging"; 9 | pub(crate) const LIBRARY_METADATA_FILENAME: &str = ".rv.metadata"; 10 | 11 | /// How long are the package databases cached for 12 | /// Same default value as PKGCACHE_TIMEOUT: 13 | /// https://github.com/r-lib/pkgcache?tab=readme-ov-file#package-environment-variables 14 | pub const PACKAGE_TIMEOUT: u64 = 60 * 60; 15 | pub const PACKAGE_TIMEOUT_ENV_VAR_NAME: &str = "PKGCACHE_TIMEOUT"; 16 | pub const PACKAGE_DB_FILENAME: &str = "packages.bin"; 17 | 18 | pub const NUM_CPUS_ENV_VAR_NAME: &str = "RV_NUM_CPUS"; 19 | pub const SYS_REQ_URL_ENV_VAR_NAME: &str = "RV_SYS_REQ_URL"; 20 | 21 | // List obtained from the REPL: `rownames(installed.packages(priority="base"))` 22 | // Those will have the same version as R 23 | pub(crate) const BASE_PACKAGES: [&str; 14] = [ 24 | "base", 25 | "compiler", 26 | "datasets", 27 | "grDevices", 28 | "graphics", 29 | "grid", 30 | "methods", 31 | "parallel", 32 | "splines", 33 | "stats", 34 | "stats4", 35 | "tcltk", 36 | "tools", 37 | "utils", 38 | ]; 39 | 40 | // List obtained from the REPL: `rownames(installed.packages(priority="recommended"))` 41 | // Those are versioned separately from R and some packages might have version requirements on them 42 | pub(crate) const RECOMMENDED_PACKAGES: [&str; 15] = [ 43 | "boot", 44 | "class", 45 | "cluster", 46 | "codetools", 47 | "foreign", 48 | "KernSmooth", 49 | "lattice", 50 | "MASS", 51 | "Matrix", 52 | "mgcv", 53 | "nlme", 54 | "nnet", 55 | "rpart", 56 | "spatial", 57 | "survival", 58 | ]; 59 | 60 | pub(crate) const ACTIVATE_FILE_TEMPLATE: &str = r#"local({%global wd content% 61 | rv_info <- system2("%rv command%", c("info", "--library", "--r-version", "--repositories"), stdout = TRUE) 62 | if (!is.null(attr(rv_info, "status"))) { 63 | # if system2 fails it'll add a status attribute with the error code 64 | warning("failed to run rv info, check your console for messages") 65 | } else { 66 | # extract library, r-version, and repositories from rv 67 | rv_lib <- sub("library: (.+)", "\\1", grep("^library:", rv_info, value = TRUE)) 68 | rv_r_ver <- sub("r-version: (.+)", "\\1", grep("^r-version:", rv_info, value = TRUE)) 69 | repo_str <- sub("repositories: ", "", grep("^repositories:", rv_info, value = TRUE)) 70 | repo_entries <- gsub("[()]", "", strsplit(repo_str, "), (", fixed = TRUE)[[1]]) 71 | repo_list <- trimws(sub(".*, ", "", repo_entries)) # Extract URL 72 | names(repo_list) <- trimws(sub(", .*", "", repo_entries)) # Extract Name 73 | # this might not yet exist, so we'll normalize it but not force it to exist 74 | # and we create it below as needed 75 | rv_lib <- normalizePath(rv_lib, mustWork = FALSE) 76 | if (!dir.exists(rv_lib)) { 77 | message("creating rv library: ", rv_lib) 78 | dir.create(rv_lib, recursive = TRUE) 79 | } 80 | .libPaths(rv_lib, include.site = FALSE) 81 | options(repos = repo_list) 82 | 83 | if (interactive()) { 84 | message("rv libpaths active!\nlibrary paths: \n", paste0(" ", .libPaths(), collapse = "\n"), "\n") 85 | message("rv repositories active!\nrepositories: \n", paste0(" ", names(getOption("repos")), ": ", getOption("repos"), collapse = "\n")) 86 | sys_r <- sprintf("%s.%s", R.version$major, R.version$minor) 87 | if (!grepl(paste0("^", rv_r_ver), sys_r)) { 88 | message(sprintf("\nWARNING: R version specified in config (%s) does not match session version (%s)", rv_r_ver, sys_r)) 89 | } 90 | } 91 | } 92 | }) 93 | "#; 94 | 95 | pub(crate) const RVR_FILE_CONTENT: &str = r#".rv <- new.env() 96 | .rv$config_path <- file.path(normalizePath(getwd()), "rproject.toml") 97 | .rv$summary <- function(json = FALSE) { 98 | command <- c("summary") 99 | if (json) { command <- c(command, "--json") } 100 | .rv$command(command) 101 | } 102 | .rv$plan <- function() { .rv$command("plan") } 103 | .rv$sync <- function() { .rv$command("sync") } 104 | .rv$add <- function(..., dry_run = FALSE) { 105 | dots <- unlist(list(...)) 106 | command <- c("add", dots) 107 | if (dry_run) { command <- c(command, "--dry-run") } 108 | .rv$command(command) 109 | } 110 | 111 | .rv$command <- function(command) { 112 | # underlying system calls to rv 113 | args <- c(command, "-c", .rv$config_path) 114 | res <- system2("rv", args, stdout = TRUE) 115 | if (!is.null(attr(res, "status"))) { 116 | warning(sprintf("failed to run `rv %s`, check your console for messages", paste(args, collapse = " "))) 117 | } else { 118 | message(paste(res, collapse = "\n")) 119 | } 120 | }"#; 121 | -------------------------------------------------------------------------------- /src/git/mod.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | mod local; 4 | mod reference; 5 | mod remote; 6 | pub(crate) mod url; 7 | 8 | pub trait CommandExecutor { 9 | fn execute(&self, command: &mut Command) -> Result; 10 | } 11 | 12 | pub use local::GitRepository; 13 | pub use reference::GitReference; 14 | pub use remote::GitRemote; 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct GitExecutor; 18 | 19 | impl CommandExecutor for GitExecutor { 20 | fn execute(&self, command: &mut Command) -> Result { 21 | let res = command.output()?; 22 | if res.status.success() { 23 | Ok(String::from_utf8_lossy(&res.stdout).trim().to_string()) 24 | } else { 25 | Err(std::io::Error::new( 26 | std::io::ErrorKind::Other, 27 | String::from_utf8_lossy(&res.stderr), 28 | )) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/git/reference.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// What a git URL can point to 4 | /// If it's coming from a lockfile, it will always be a commit 5 | #[derive(Debug, Clone, PartialEq, Eq)] 6 | pub enum GitReference<'g> { 7 | /// A specific branch 8 | Branch(&'g str), 9 | /// A specific tag. 10 | Tag(&'g str), 11 | /// The commit hash 12 | Commit(&'g str), 13 | /// We don't know what it is. 14 | /// Used for Remotes 15 | Unknown(&'g str), 16 | } 17 | 18 | impl<'g> GitReference<'g> { 19 | pub fn reference(&self) -> &'g str { 20 | match self { 21 | GitReference::Branch(b) => b, 22 | GitReference::Tag(b) => b, 23 | GitReference::Commit(b) => b, 24 | GitReference::Unknown(b) => b, 25 | } 26 | } 27 | 28 | /// We return multiple possible refspec because for package remotes we don't actually know what it 29 | /// so we will try everything 30 | pub fn as_refspecs(&self) -> Vec { 31 | match self { 32 | GitReference::Branch(branch) => { 33 | vec![format!("+refs/heads/{branch}:refs/remotes/origin/{branch}")] 34 | } 35 | GitReference::Tag(tag) => { 36 | vec![format!("+refs/tags/{tag}:refs/remotes/origin/tags/{tag}")] 37 | } 38 | GitReference::Commit(rev) => vec![format!("+{rev}:refs/commit/{rev}")], 39 | GitReference::Unknown(_) => { 40 | // We don't know, just fetch everything 41 | vec![ 42 | String::from("+refs/heads/*:refs/remotes/origin/*"), 43 | String::from("+HEAD:refs/remotes/origin/HEAD"), 44 | ] 45 | } 46 | } 47 | } 48 | } 49 | 50 | impl fmt::Display for GitReference<'_> { 51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 52 | write!(f, "{}", self.reference()) 53 | } 54 | } 55 | 56 | #[derive(Debug, Clone, PartialEq, Eq)] 57 | pub struct Oid(String); 58 | 59 | impl Oid { 60 | pub fn new(s: String) -> Oid { 61 | Oid(s) 62 | } 63 | 64 | pub fn as_str(&self) -> &str { 65 | self.0.as_str() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/git/remote.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use crate::git::{CommandExecutor, GitReference, GitRepository}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct GitRemote { 7 | url: String, 8 | directory: Option, 9 | } 10 | 11 | impl GitRemote { 12 | pub fn new(url: &str) -> Self { 13 | Self { 14 | url: url.to_string(), 15 | directory: None, 16 | } 17 | } 18 | 19 | pub fn set_directory(&mut self, directory: &str) { 20 | self.directory = Some(PathBuf::from(directory)); 21 | } 22 | 23 | /// Fetch the minimum possible to only get the DESCRIPTION file. 24 | /// If the repository is already in the cache at `full_dest`, just checkout the reference and use that 25 | /// This will return the body of the DESCRIPTION file if there was one as well as the oid. 26 | /// Only used during resolution 27 | pub fn sparse_checkout_for_description( 28 | &self, 29 | dest: impl AsRef, 30 | reference: &GitReference, 31 | executor: impl CommandExecutor + Clone + 'static, 32 | ) -> Result<(String, String), std::io::Error> { 33 | // If we have it locally try to only fetch what's needed 34 | if dest.as_ref().is_dir() { 35 | let local = GitRepository::open(dest.as_ref(), &self.url, executor)?; 36 | local.fetch(&self.url, reference)?; 37 | let content = local.get_description_file_content( 38 | &self.url, 39 | reference, 40 | self.directory.as_ref(), 41 | )?; 42 | let oid = local.ref_as_oid(reference.reference()).unwrap(); 43 | Ok((oid.as_str().to_string(), content)) 44 | } else { 45 | let local = GitRepository::init(dest.as_ref(), &self.url, executor)?; 46 | local.fetch(&self.url, reference)?; 47 | match local.sparse_checkout(&self.url, reference) { 48 | Ok(_) => (), 49 | Err(e) => { 50 | // Ensure we delete the folder so another resolution will not find it 51 | local.rm_folder()?; 52 | return Err(e); 53 | } 54 | } 55 | 56 | let content = local.get_description_file_content( 57 | &self.url, 58 | reference, 59 | self.directory.as_ref(), 60 | )?; 61 | let oid = local.ref_as_oid(reference.reference()).unwrap(); 62 | Ok((oid.as_str().to_string(), content)) 63 | } 64 | } 65 | 66 | pub fn checkout( 67 | &self, 68 | dest: impl AsRef, 69 | reference: &GitReference, 70 | executor: impl CommandExecutor + Clone + 'static, 71 | ) -> Result<(), std::io::Error> { 72 | let repo = if dest.as_ref().is_dir() { 73 | GitRepository::open(dest.as_ref(), &self.url, executor)? 74 | } else { 75 | GitRepository::init(dest.as_ref(), &self.url, executor)? 76 | }; 77 | 78 | repo.disable_sparse_checkout()?; 79 | repo.fetch(&self.url, reference)?; 80 | if let Some(o) = repo.ref_as_oid(reference.reference()) { 81 | repo.checkout(&o)?; 82 | } else { 83 | return Err(std::io::Error::new( 84 | std::io::ErrorKind::Other, 85 | format!("Failed to find reference {:?}", reference), 86 | )); 87 | } 88 | 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/git/url.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use bincode::de::Decoder; 4 | use bincode::enc::Encoder; 5 | use bincode::error::{DecodeError, EncodeError}; 6 | use bincode::{Decode, Encode}; 7 | use serde::{Deserialize, Deserializer, Serialize}; 8 | use url::Url; 9 | 10 | #[derive(Clone, PartialEq, Eq, Serialize)] 11 | #[serde(untagged)] 12 | pub enum GitUrl { 13 | Http(Url), 14 | Ssh(String), 15 | } 16 | 17 | impl TryFrom<&str> for GitUrl { 18 | type Error = String; 19 | 20 | fn try_from(s: &str) -> Result { 21 | let s2 = s.trim(); 22 | if s2.is_empty() { 23 | return Err("URL for git cannot be empty".to_string()); 24 | } 25 | 26 | // Should we support user@ syntax? 27 | if s2.starts_with("git@") || s2.starts_with("ssh@") { 28 | return Ok(GitUrl::Ssh(s.to_string())); 29 | } 30 | 31 | // Try to parse as a standard URL 32 | if s2.starts_with("http://") || s2.starts_with("https://") { 33 | if let Ok(url) = Url::parse(s2) { 34 | return Ok(GitUrl::Http(url)); 35 | } 36 | } 37 | 38 | Err(format!("Invalid URL format for git: {s}")) 39 | } 40 | } 41 | 42 | impl<'de> Deserialize<'de> for GitUrl { 43 | fn deserialize(deserializer: D) -> Result 44 | where 45 | D: Deserializer<'de>, 46 | { 47 | let s = String::deserialize(deserializer)?; 48 | match Self::try_from(s.as_str()) { 49 | Ok(url) => Ok(url), 50 | Err(e) => Err(serde::de::Error::custom(e)), 51 | } 52 | } 53 | } 54 | 55 | impl Encode for GitUrl { 56 | fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { 57 | match self { 58 | Self::Http(_) => { 59 | 0u8.encode(encoder)?; 60 | self.url().encode(encoder) 61 | } 62 | Self::Ssh(_) => { 63 | 1u8.encode(encoder)?; 64 | self.url().encode(encoder) 65 | } 66 | } 67 | } 68 | } 69 | 70 | impl Decode for GitUrl { 71 | fn decode>(decoder: &mut D) -> Result { 72 | let variant = u8::decode(decoder)?; 73 | let url = String::decode(decoder)?; 74 | 75 | match variant { 76 | 0 => Ok(Self::Http(Url::parse(&url).expect("valid URL"))), 77 | 1 => Ok(Self::Ssh(url)), 78 | _ => unreachable!("invalid variant 0x{:02x}", variant), 79 | } 80 | } 81 | } 82 | 83 | // A bit sad we need to duplicate it 84 | impl<'de, Context> bincode::BorrowDecode<'de, Context> for GitUrl { 85 | fn borrow_decode>( 86 | decoder: &mut D, 87 | ) -> Result { 88 | let variant = u8::decode(decoder)?; 89 | let url = String::decode(decoder)?; 90 | 91 | match variant { 92 | 0 => Ok(Self::Http(Url::parse(&url).expect("valid URL"))), 93 | 1 => Ok(Self::Ssh(url)), 94 | _ => unreachable!("invalid variant 0x{:02x}", variant), 95 | } 96 | } 97 | } 98 | 99 | impl GitUrl { 100 | pub fn url(&self) -> &str { 101 | match self { 102 | Self::Http(url) => url.as_str(), 103 | Self::Ssh(url) => url.as_str(), 104 | } 105 | } 106 | } 107 | 108 | impl fmt::Display for GitUrl { 109 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 110 | write!(f, "{}", self.url()) 111 | } 112 | } 113 | 114 | impl fmt::Debug for GitUrl { 115 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 116 | write!(f, "\"{}\"", self.url()) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod activate; 2 | mod add; 3 | mod cache; 4 | mod config; 5 | mod fs; 6 | mod git; 7 | mod http; 8 | mod library; 9 | mod lockfile; 10 | mod package; 11 | mod project_summary; 12 | mod r_cmd; 13 | mod renv; 14 | mod repository; 15 | mod repository_urls; 16 | mod resolver; 17 | mod sync; 18 | mod system_info; 19 | pub mod system_req; 20 | mod utils; 21 | 22 | #[cfg(feature = "cli")] 23 | pub mod cli; 24 | 25 | pub mod consts; 26 | 27 | pub use activate::{activate, deactivate}; 28 | pub use add::{add_packages, read_and_verify_config}; 29 | pub use cache::{CacheInfo, DiskCache, PackagePaths, utils::hash_string}; 30 | pub use config::{Config, ConfigDependency, Repository}; 31 | pub use git::{CommandExecutor, GitExecutor, GitRepository}; 32 | pub use http::{Http, HttpDownload}; 33 | pub use library::Library; 34 | pub use lockfile::Lockfile; 35 | pub use package::{Version, VersionRequirement, is_binary_package}; 36 | pub use project_summary::ProjectSummary; 37 | pub use r_cmd::{RCmd, RCommandLine, find_r_version_command}; 38 | pub use renv::RenvLock; 39 | pub use repository::RepositoryDatabase; 40 | pub use repository_urls::{get_package_file_urls, get_tarball_urls}; 41 | pub use resolver::{ResolvedDependency, Resolver, UnresolvedDependency}; 42 | pub use sync::{BuildPlan, BuildStep, SyncChange, SyncHandler}; 43 | pub use system_info::{OsType, SystemInfo}; 44 | -------------------------------------------------------------------------------- /src/package/builtin.rs: -------------------------------------------------------------------------------- 1 | use crate::RCmd; 2 | use crate::consts::{BASE_PACKAGES, RECOMMENDED_PACKAGES}; 3 | use crate::package::{Package, parse_description_file_in_folder}; 4 | use bincode::{Decode, Encode}; 5 | use fs_err as fs; 6 | use std::collections::HashMap; 7 | use std::io::{BufReader, BufWriter}; 8 | use std::path::Path; 9 | 10 | #[derive(Debug, Clone, Default, Encode, Decode)] 11 | pub struct BuiltinPackages { 12 | pub(crate) packages: HashMap, 13 | } 14 | 15 | impl BuiltinPackages { 16 | /// If we fail to read it, consider we don't have it, no need to error 17 | pub fn load(path: impl AsRef) -> Option { 18 | let reader = BufReader::new(std::fs::File::open(path.as_ref()).ok()?); 19 | 20 | bincode::decode_from_reader(reader, bincode::config::standard()).ok() 21 | } 22 | 23 | pub fn persist(&self, path: impl AsRef) -> std::io::Result<()> { 24 | if let Some(parent) = path.as_ref().parent() { 25 | std::fs::create_dir_all(parent)?; 26 | } 27 | let mut writer = BufWriter::new(std::fs::File::create(path.as_ref())?); 28 | bincode::encode_into_std_write(self, &mut writer, bincode::config::standard()) 29 | .expect("valid data"); 30 | 31 | Ok(()) 32 | } 33 | } 34 | 35 | pub fn get_builtin_versions_from_library(r_cmd: &impl RCmd) -> std::io::Result { 36 | match r_cmd.get_r_library() { 37 | Ok(p) => { 38 | let mut builtins = BuiltinPackages::default(); 39 | for entry in fs::read_dir(p)? { 40 | let entry = entry?; 41 | match parse_description_file_in_folder(entry.path()) { 42 | Ok(p) => { 43 | if BASE_PACKAGES.contains(&p.name.as_str()) 44 | || RECOMMENDED_PACKAGES.contains(&p.name.as_str()) 45 | { 46 | builtins.packages.insert(p.name.clone(), p); 47 | } 48 | } 49 | Err(e) => { 50 | log::error!( 51 | "Error parsing description file in {:?}: {}", 52 | entry.path(), 53 | e 54 | ); 55 | continue; 56 | } 57 | } 58 | } 59 | Ok(builtins) 60 | } 61 | Err(e) => { 62 | log::error!("Failed to find library: {e}"); 63 | Ok(BuiltinPackages::default()) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/package/description.rs: -------------------------------------------------------------------------------- 1 | use crate::Version; 2 | use crate::consts::DESCRIPTION_FILENAME; 3 | use crate::package::Package; 4 | use crate::package::parser::parse_package_file; 5 | use std::fs; 6 | use std::fs::File; 7 | use std::io::BufRead; 8 | use std::path::Path; 9 | use std::str::FromStr; 10 | 11 | /// A DESCRIPTION file is like a PACKAGE file, only that it contains info about a single package 12 | pub fn parse_description_file(content: &str) -> Option { 13 | // TODO: handle remotes in package for deps 14 | let new_content = content.to_string() + "\n"; 15 | 16 | let packages = parse_package_file(new_content.as_str()); 17 | packages 18 | .into_values() 19 | .next() 20 | .and_then(|p| p.into_iter().next()) 21 | } 22 | 23 | pub fn parse_description_file_in_folder( 24 | folder: impl AsRef, 25 | ) -> Result> { 26 | let folder = folder.as_ref(); 27 | let description_path = folder.join(DESCRIPTION_FILENAME); 28 | 29 | match fs::read_to_string(&description_path) { 30 | Ok(content) => { 31 | if let Some(package) = parse_description_file(&content) { 32 | Ok(package) 33 | } else { 34 | Err(format!("Invalid DESCRIPTION file at {}", description_path.display()).into()) 35 | } 36 | } 37 | Err(e) => Err(format!( 38 | "Could not read destination file at {} {e}", 39 | description_path.display() 40 | ) 41 | .into()), 42 | } 43 | } 44 | 45 | /// Quick version that only cares about retrieving the version of a package and ignores everything else 46 | pub fn parse_version(file_path: impl AsRef) -> Result> { 47 | let file = File::open(file_path)?; 48 | for line in std::io::BufReader::new(file).lines().map_while(Result::ok) { 49 | if let Some(stripped) = line.strip_prefix("Version:") { 50 | return Ok(Version::from_str(stripped.trim()).expect("Version should be parsable")); 51 | } 52 | } 53 | 54 | Err("Version not found.".into()) 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | use crate::package::remotes::PackageRemote; 61 | 62 | #[test] 63 | fn can_parse_description_file() { 64 | let content = fs::read_to_string("src/tests/descriptions/gsm.app.DESCRIPTION").unwrap(); 65 | let package = parse_description_file(&content).unwrap(); 66 | assert_eq!(package.name, "gsm.app"); 67 | assert_eq!(package.version.original, "2.3.0.9000"); 68 | assert_eq!(package.imports.len(), 15); 69 | assert_eq!(package.suggests.len(), 11); 70 | assert_eq!(package.remotes.len(), 1); 71 | println!("{:#?}", package.remotes); 72 | match &package.remotes["gsm=gilead-biostats/gsm@v2.2.2"] { 73 | (name, PackageRemote::Git { url, .. }) => { 74 | assert_eq!(url.url(), "https://github.com/gilead-biostats/gsm"); 75 | assert_eq!(name, &Some("gsm".to_string())); 76 | } 77 | _ => panic!("Should have gotten a git repo"), 78 | } 79 | } 80 | 81 | #[test] 82 | fn can_read_version() { 83 | let version = parse_version("src/tests/descriptions/gsm.app.DESCRIPTION").unwrap(); 84 | assert_eq!(version.original, "2.3.0.9000"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/package/mod.rs: -------------------------------------------------------------------------------- 1 | use bincode::{Decode, Encode}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashMap; 4 | use std::fmt; 5 | use std::path::Path; 6 | use toml_edit::{InlineTable, Value}; 7 | 8 | mod builtin; 9 | mod description; 10 | mod parser; 11 | mod remotes; 12 | mod version; 13 | 14 | use crate::consts::BASE_PACKAGES; 15 | pub use builtin::{BuiltinPackages, get_builtin_versions_from_library}; 16 | pub use description::{parse_description_file, parse_description_file_in_folder, parse_version}; 17 | pub use parser::parse_package_file; 18 | pub use remotes::PackageRemote; 19 | pub use version::{Operator, Version, VersionRequirement, deserialize_version}; 20 | 21 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Encode, Decode, Serialize)] 22 | #[serde(rename_all = "lowercase")] 23 | pub enum PackageType { 24 | Source, 25 | Binary, 26 | } 27 | 28 | impl fmt::Display for PackageType { 29 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 | match self { 31 | Self::Source => write!(f, "source"), 32 | Self::Binary => write!(f, "binary"), 33 | } 34 | } 35 | } 36 | 37 | #[derive(Debug, Hash, Eq, PartialEq, Clone, Encode, Decode, Serialize, Deserialize)] 38 | #[serde(untagged)] 39 | pub enum Dependency { 40 | Simple(String), 41 | Pinned { 42 | name: String, 43 | requirement: VersionRequirement, 44 | }, 45 | } 46 | 47 | impl Dependency { 48 | pub(crate) fn name(&self) -> &str { 49 | match self { 50 | Dependency::Simple(s) => s, 51 | Dependency::Pinned { name, .. } => name, 52 | } 53 | } 54 | 55 | pub(crate) fn version_requirement(&self) -> Option<&VersionRequirement> { 56 | match self { 57 | Dependency::Simple(_) => None, 58 | Dependency::Pinned { requirement, .. } => Some(requirement), 59 | } 60 | } 61 | 62 | pub(crate) fn as_toml_value(&self) -> Value { 63 | match self { 64 | Self::Simple(name) => Value::from(name.as_str()), 65 | Self::Pinned { name, requirement } => { 66 | let mut table = InlineTable::new(); 67 | table.insert("name", Value::from(name.as_str())); 68 | table.insert("requirement", Value::from(&requirement.to_string())); 69 | Value::InlineTable(table) 70 | } 71 | } 72 | } 73 | } 74 | 75 | #[derive(Debug, Default, PartialEq, Clone, Encode, Decode)] 76 | pub struct Package { 77 | pub(crate) name: String, 78 | pub(crate) version: Version, 79 | r_requirement: Option, 80 | depends: Vec, 81 | imports: Vec, 82 | suggests: Vec, 83 | enhances: Vec, 84 | linking_to: Vec, 85 | license: String, 86 | md5_sum: String, 87 | pub(crate) path: Option, 88 | recommended: bool, 89 | pub(crate) needs_compilation: bool, 90 | // {remote_string => (pkg name, remote)} 91 | pub(crate) remotes: HashMap, PackageRemote)>, 92 | } 93 | 94 | #[derive(Debug, Default, PartialEq, Clone)] 95 | pub struct InstallationDependencies<'a> { 96 | pub(crate) direct: Vec<&'a Dependency>, 97 | pub(crate) suggests: Vec<&'a Dependency>, 98 | } 99 | 100 | impl Package { 101 | #[inline] 102 | pub fn works_with_r_version(&self, r_version: &Version) -> bool { 103 | if let Some(r_req) = &self.r_requirement { 104 | r_req.is_satisfied(r_version) 105 | } else { 106 | true 107 | } 108 | } 109 | 110 | pub fn r_version_requirement(&self) -> Option<&VersionRequirement> { 111 | self.r_requirement.as_ref() 112 | } 113 | 114 | pub fn dependencies_to_install(&self, install_suggestions: bool) -> InstallationDependencies { 115 | let mut out = Vec::with_capacity(30); 116 | // TODO: consider if this should be an option or just take it as an empty vector otherwise 117 | out.extend(self.depends.iter()); 118 | out.extend(self.imports.iter()); 119 | 120 | // The deps in linkingTo can be listed already in depends 121 | for dep in &self.linking_to { 122 | if out.iter().find(|x| x.name() == dep.name()).is_none() { 123 | out.push(dep); 124 | } 125 | } 126 | 127 | let suggests = if install_suggestions { 128 | self.suggests 129 | .iter() 130 | .filter(|p| !BASE_PACKAGES.contains(&p.name())) 131 | .collect() 132 | } else { 133 | Vec::new() 134 | }; 135 | 136 | InstallationDependencies { 137 | direct: out 138 | .into_iter() 139 | .filter(|p| !BASE_PACKAGES.contains(&p.name())) 140 | .collect(), 141 | suggests, 142 | } 143 | } 144 | } 145 | 146 | /// Returns whether this folder contains compiled R files 147 | pub fn is_binary_package(path: impl AsRef, name: &str) -> bool { 148 | path.as_ref().join("R").join(format!("{name}.rdx")).exists() 149 | } 150 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-10.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "bitbucket::sulab/mygene.r@default" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("mygene.r") => Git { url: "https://bitbucket.org/sulab/mygene.r", reference: Some("default"), pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-11.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "bioc::3.3/SummarizedExperiment#117513" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | None => Bioc("3.3/SummarizedExperiment#117513") 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-12.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "svn::https://github.com/tidyverse/stringr" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | None => Other("https://github.com/tidyverse/stringr") 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-13.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "url::https://github.com/tidyverse/stringr/archive/HEAD.zip" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | None => Url("https://github.com/tidyverse/stringr/archive/HEAD.zip") 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-14.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "local::/pkgs/testthat" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | None => Local("/pkgs/testthat") 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-15.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: clindata=Gilead-BioStats/clindata 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("clindata") => Git { url: "https://github.com/Gilead-BioStats/clindata", reference: None, pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-16.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: yaml=vubiostat/r-yaml 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("yaml") => Git { url: "https://github.com/vubiostat/r-yaml", reference: None, pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-17.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: insightsengineering/teal.data 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("teal.data") => Git { url: "https://github.com/insightsengineering/teal.data", reference: None, pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-18.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: dmlc/xgboost/R-package 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("xgboost") => Git { url: "https://github.com/dmlc/xgboost/R-package", reference: None, pull_request: None, directory: Some("R-package") } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: r-lib/httr@v0.4 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("httr") => Git { url: "https://github.com/r-lib/httr", reference: Some("v0.4"), pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: r-lib/testthat@c67018fa4970 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("testthat") => Git { url: "https://github.com/r-lib/testthat", reference: Some("c67018fa4970"), pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "klutometis/roxygen#142" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("roxygen") => Git { url: "https://github.com/klutometis/roxygen", reference: None, pull_request: Some("142"), directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "github::tidyverse/ggplot2" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("ggplot2") => Git { url: "https://github.com/tidyverse/ggplot2", reference: None, pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-6.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "gitlab::jimhester/covr" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("covr") => Git { url: "https://gitlab.com/jimhester/covr", reference: None, pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-7.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "git::git@bitbucket.org:djnavarro/lsr.git" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("lsr") => Git { url: "git@bitbucket.org:djnavarro/lsr.git", reference: None, pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-8.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "git::git@github.com:username/repo.git@a1b2c3d4" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("repo") => Git { url: "git@github.com:username/repo.git", reference: Some("a1b2c3d4"), pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes-9.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: "git::https://github.com/igraph/rigraph.git@main" 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | None => Git { url: "https://github.com/igraph/rigraph.git", reference: Some("main"), pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/package/snapshots/rv__package__remotes__tests__can_parse_remotes.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/package/remotes.rs 3 | description: r-lib/testthat 4 | expression: "format!(\"{name:?} => {remote:?}\")" 5 | --- 6 | Some("testthat") => Git { url: "https://github.com/r-lib/testthat", reference: None, pull_request: None, directory: None } 7 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__dependency__test__R Universe arrow API parse.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/dependency.rs 3 | expression: api 4 | --- 5 | RUniverseApi { 6 | remote_url: "https://github.com/apache/arrow", 7 | remote_sha: "3ad0370a04ccdae638755b94c3c31c8760a11193", 8 | remote_subdir: Some( 9 | "r", 10 | ), 11 | } 12 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__dependency__test__R Universe scicalc API parse.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/dependency.rs 3 | expression: api 4 | --- 5 | RUniverseApi { 6 | remote_url: "https://github.com/a2-ai/osinfo", 7 | remote_sha: "f815095b7b04cbf57da0e0c0a55ef5e03c16f477", 8 | remote_subdir: None, 9 | } 10 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__case_sensitive.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | --- unresolved --- 6 | r6 [listed in rproject.toml] 7 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__conflict_dep_requirement.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | rv.git.pkgD=0.0.1 (repository(url: http://repo1/), type=source, path='', from_lockfile=false, from_remote=false) 6 | rv.git.pkgA=0.0.4 (repository(url: http://repo2/), type=source, path='', from_lockfile=false, from_remote=false) 7 | --- requirement failures --- 8 | rv.git.pkgA : rv.git.pkgD requires (>= 0.0.5) 9 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__conflict_dep_requirement2.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | rv.git.pkgD=0.0.1 (repository(url: http://repo1/), type=source, path='', from_lockfile=false, from_remote=false) 6 | rv.git.pkgE=0.0.0.1 (repository(url: http://repo1/), type=source, path='', from_lockfile=false, from_remote=false) 7 | rv.git.pkgA=0.0.5 (repository(url: http://repo1/), type=source, path='', from_lockfile=false, from_remote=false) 8 | rv.git.pkgA=0.0.4 (repository(url: http://repo2/), type=source, path='', from_lockfile=false, from_remote=false) 9 | --- requirement failures --- 10 | rv.git.pkgA : rv.git.pkgD requires (>= 0.0.5), rv.git.pkgE requires (< 0.0.5) 11 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__dep_force_source_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | R6=2.5.1 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__dep_force_source_no_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | R6=2.5.1 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__dep_force_source_override_repo.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | dplyr=1.1.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 6 | cli=3.6.3 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 7 | generics=0.1.3 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 8 | glue=1.8.0 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 9 | lifecycle=1.0.4 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 10 | magrittr=2.0.3 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 11 | pillar=1.9.0 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 12 | R6=2.5.1 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 13 | rlang=1.1.4 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 14 | tibble=3.2.1 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 15 | tidyselect=1.2.1 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 16 | vctrs=0.6.5 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 17 | fansi=1.0.6 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 18 | utf8=1.2.4 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 19 | pkgconfig=2.0.3 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 20 | withr=3.0.2 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 21 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__dependencies_only.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | cli=3.6.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 6 | generics=0.1.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 7 | glue=1.8.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 8 | lifecycle=1.0.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 9 | magrittr=2.0.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 10 | pillar=1.10.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 11 | R6=2.5.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 12 | rlang=1.1.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 13 | tibble=3.2.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 14 | tidyselect=1.2.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 15 | vctrs=0.6.5 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 16 | utf8=1.2.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 17 | fansi=1.0.6 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 18 | pkgconfig=2.0.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 19 | withr=3.0.2 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 20 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__different_repo_from_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | R6=2.5.1 (repository(url: http://test/), type=binary, path='', from_lockfile=false, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__dplyr_no_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | dplyr=1.1.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 6 | cli=3.6.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 7 | generics=0.1.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 8 | glue=1.8.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 9 | lifecycle=1.0.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 10 | magrittr=2.0.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 11 | pillar=1.10.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 12 | R6=2.5.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 13 | rlang=1.1.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 14 | tibble=3.2.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 15 | tidyselect=1.2.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 16 | vctrs=0.6.5 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 17 | utf8=1.2.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 18 | fansi=1.0.6 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 19 | pkgconfig=2.0.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 20 | withr=3.0.2 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 21 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__dupe_imports_linktsto.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | httpuv=1.6.15 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 6 | later=1.4.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 7 | promises=1.3.2 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 8 | R6=2.5.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 9 | Rcpp=1.0.13-1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 10 | rlang=1.1.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 11 | fastmap=1.2.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 12 | magrittr=2.0.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 13 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__git_pkg_wrong_name.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | --- unresolved --- 6 | wrong [listed in rproject.toml]: "Found package `gsm` from https://github.com/Gilead-BioStats/gsm but it is called `wrong` in the rproject.toml" 7 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__higher_r_version.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | cluster=2.1.7 (repository(url: http://posit/), type=binary, path='4.5.0/Recommended', from_lockfile=false, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__local_dep.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | dummy=0.0.0.9001 (local(path: dummy-pkg), type=source, path='', from_lockfile=false, from_remote=false) 6 | R6=2.5.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 7 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__local_dep_not_found.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | --- unresolved --- 6 | dummy [listed in rproject.toml]: "unknown doesn't exist." 7 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__local_dep_wrong_name.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | --- unresolved --- 6 | wrong [listed in rproject.toml]: "Found package `dummy` from dummy-pkg but it is called `wrong` in the rproject.toml" 7 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__multi-repo-combination-no-suggests.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | slurmtools=0.1.2 (repository(url: http://a2-ai-universe/), type=source, path='', from_lockfile=false, from_remote=false) 6 | brio=1.1.5 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 7 | dplyr=1.1.4 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 8 | fs=1.6.5 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 9 | glue=1.8.0 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 10 | jsonlite=1.8.9 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 11 | processx=3.8.4 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 12 | purrr=1.0.2 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 13 | rlang=1.1.4 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 14 | tibble=3.2.1 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 15 | whisker=0.4.1 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 16 | withr=3.0.2 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 17 | here=1.0.1 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 18 | cli=3.6.3 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 19 | stringi=1.8.4 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 20 | hms=1.1.3 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 21 | lifecycle=1.0.4 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 22 | generics=0.1.3 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 23 | magrittr=2.0.3 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 24 | pillar=1.9.0 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 25 | R6=2.5.1 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 26 | tidyselect=1.2.1 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 27 | vctrs=0.6.5 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 28 | ps=1.8.1 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 29 | fansi=1.0.6 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 30 | pkgconfig=2.0.3 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 31 | rprojroot=2.0.4 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 32 | utf8=1.2.4 (repository(url: http://p3m/), type=binary, path='', from_lockfile=false, from_remote=false) 33 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__path.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | MASS=7.3-61 (repository(url: http://posit/), type=source, path='4.5.0/Recommended', from_lockfile=false, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__r_universe_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | osinfo=0.0.1 (runiverse(repo: http://r-universe.dev/, url: https://github.com/a2-ai/osinfo, sha: f815095b7b04cbf57da0e0c0a55ef5e03c16f477, directory: None), type=binary, path='', from_lockfile=true, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__r_universe_no_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | osinfo=0.0.1 (repository(url: http://r-universe.dev/), type=source, path='', from_lockfile=false, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__recommended_from_repo_w_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | MASS=7.3-61 (repository(url: http://posit/), type=source, path='', from_lockfile=false, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__recommended_pkg_specified.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | higher-base-pkg=0.0.1 (repository(url: http://repo1/), type=source, path='', from_lockfile=false, from_remote=false) 6 | survival=6.0.0 (repository(url: http://repo1/), type=source, path='', from_lockfile=false, from_remote=false) 7 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__recommended_pkg_specified_from_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | higher-base-pkg=0.0.1 (repository(url: http://repo1/), type=source, path='', from_lockfile=false, from_remote=false) 6 | survival=6.0.0 (repository(url: http://repo1/), type=source, path='', from_lockfile=false, from_remote=false) 7 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__repo_priority_order.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | texPreview=2.1.0 (repository(url: http://gh-pkg-mirror/), type=binary, path='', from_lockfile=false, from_remote=false) 6 | base64enc=0.1-3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 7 | details=0.3.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 8 | fs=1.6.5 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 9 | htmltools=0.5.8.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 10 | knitr=1.49 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 11 | magick=2.8.5 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 12 | rematch2=2.1.2 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 13 | rstudioapi=0.17.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 14 | svgPanZoom=0.3.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 15 | whisker=0.4.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 16 | xml2=1.3.6 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 17 | tinytex=0.54 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 18 | clipr=0.8.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 19 | desc=1.4.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 20 | httr=1.4.7 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 21 | magrittr=2.0.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 22 | png=0.1-8 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 23 | withr=3.0.2 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 24 | digest=0.6.37 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 25 | fastmap=1.2.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 26 | rlang=1.1.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 27 | evaluate=1.0.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 28 | highr=0.11 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 29 | xfun=0.49 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 30 | yaml=2.3.10 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 31 | Rcpp=1.0.13-1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 32 | curl=6.0.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 33 | tibble=3.2.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 34 | htmlwidgets=1.6.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 35 | cli=3.6.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 36 | R6=2.5.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 37 | jsonlite=1.8.9 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 38 | mime=0.12 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 39 | openssl=2.3.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 40 | fansi=1.0.6 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 41 | lifecycle=1.0.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 42 | pillar=1.10.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 43 | pkgconfig=2.0.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 44 | vctrs=0.6.5 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 45 | rmarkdown=2.29 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 46 | askpass=1.2.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 47 | glue=1.8.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 48 | utf8=1.2.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 49 | bslib=0.8.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 50 | fontawesome=0.5.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 51 | jquerylib=0.1.4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 52 | sys=3.4.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 53 | cachem=1.1.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 54 | memoise=2.0.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 55 | sass=0.4.9 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 56 | rappdirs=0.3.3 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 57 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__simple_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | R6=2.5.1 (repository(url: http://cran/), type=binary, path='', from_lockfile=true, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__simple_no_lockfile.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | R6=2.5.1 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__suggestions.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | A3=1.0.0 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 6 | xtable=1.8-4 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 7 | pbapply=1.7-2 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 8 | randomForest=4.7-1.2 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 9 | e1071=1.7-16 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 10 | class=7.3-22 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 11 | proxy=0.4-27 (repository(url: http://cran/), type=binary, path='', from_lockfile=false, from_remote=false) 12 | MASS=7.3-60 (builtin, type=binary, path='', from_lockfile=false, from_remote=false) 13 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__unmet_version_req.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | unmet-version-req=0.0.1 (repository(url: http://cran/), type=source, path='', from_lockfile=false, from_remote=false) 6 | --- unresolved --- 7 | zzlite (>= 1.0) [required by: unmet-version-req] 8 | --- requirement failures --- 9 | zzlite : unmet-version-req requires (>= 1.0) 10 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__url-dep.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | dplyr=1.1.3 (url(url: https://cran.r-project.org/src/contrib/Archive/dplyr/dplyr_1.1.3.tar.gz, sha:SOME_SHA), type=source, path='', from_lockfile=false, from_remote=false) 6 | cli=3.6.3 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 7 | generics=0.1.3 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 8 | glue=1.8.0 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 9 | lifecycle=1.0.4 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 10 | magrittr=2.0.3 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 11 | pillar=1.9.0 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 12 | R6=2.5.1 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 13 | rlang=1.1.4 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 14 | tibble=3.2.1 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 15 | tidyselect=1.2.1 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 16 | vctrs=0.6.5 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 17 | fansi=1.0.6 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 18 | utf8=1.2.4 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 19 | pkgconfig=2.0.3 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 20 | withr=3.0.2 (repository(url: http://posit/), type=binary, path='', from_lockfile=false, from_remote=false) 21 | -------------------------------------------------------------------------------- /src/resolver/snapshots/rv__resolver__tests__valid_from_multiple_repos.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver/mod.rs 3 | expression: out 4 | --- 5 | rv.git.pkgA=0.0.5 (repository(url: http://repo1/), type=source, path='', from_lockfile=false, from_remote=false) 6 | -------------------------------------------------------------------------------- /src/snapshots/rv__add__tests__add_remove.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/add.rs 3 | expression: doc.to_string() 4 | --- 5 | [project] 6 | name = "project_name" 7 | # Can specify which version of R is required, could be used later in rv as R version manager? 8 | r_version = "4.4.1" 9 | description = "" 10 | authors = [{name = "Bob", email="hello@acme.org", maintainer = true}] 11 | license = "MIT" 12 | keywords = [] 13 | 14 | # Are suggested deps also enforcing repository? Only used if you're making a library 15 | suggests = [] 16 | 17 | # Order matters 18 | repositories = [ 19 | { alias = "cran", url = "https://cran.r-project.org"}, 20 | { alias = "mpn", url = "https://mpn.metworx.com/snapshots/stable/2020-09-20"}, 21 | ] 22 | 23 | dependencies = [ 24 | "dplyr", 25 | { name = "some-package", repository = "mpn", install_suggestions = true }, 26 | { name = "some-package", path = "../mpn", install_suggestions = true }, 27 | { name = "some-package", git = "https://github.com/A2-ai/scicalc", tag = "v0.1.1", install_suggestions = true }, 28 | { name = "some-package", git = "https://github.com/A2-ai/scicalc", commit = "bc50e550e432c3c620714f30dd59115801f89995", install_suggestions = true }, 29 | { name = "some-package", git = "git@github.com:username/repo.git", commit = "bc50e550e432c3c620714f30dd59115801f89995", install_suggestions = true }, 30 | "pkg1", 31 | "pkg2", 32 | ] 33 | -------------------------------------------------------------------------------- /src/snapshots/rv__renv__tests__renv_resolver.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/renv.rs 3 | expression: out 4 | --- 5 | { name = "ghqc", git = "https://github.com/a2-ai/ghqc", commit = "55c23eb6a444542dab742d3d37c7b65af7b12e38" } 6 | { name = "rv.git.pkgA", path = "src/tests/renv/rv.git.pkgA_0.0.0.9000.tar.gz" } 7 | { name = "simpar", repository = "gh-pkg-mirror" } 8 | --- unresolved --- 9 | `R6` could not be resolved due to: "Package version (2.5.0) not found in repositories. Found version 2.5.1 in https://cran-binary" 10 | `slurmtools` could not be resolved due to: "Package not found in repositories" 11 | `unknown_pkg` could not be resolved due to: "Source (unknown) is not supported" 12 | -------------------------------------------------------------------------------- /src/snapshots/rv__resolver__tests__multi-repo-combination-no-suggests.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver.rs 3 | expression: out 4 | --- 5 | slurmtools=0.1.2 (from http://a2-ai-universe, type=source, from_lockfile=false, path='') 6 | brio=1.1.5 (from http://P3M, type=binary, from_lockfile=false, path='') 7 | dplyr=1.1.4 (from http://P3M, type=binary, from_lockfile=false, path='') 8 | fs=1.6.5 (from http://P3M, type=binary, from_lockfile=false, path='') 9 | glue=1.8.0 (from http://P3M, type=binary, from_lockfile=false, path='') 10 | jsonlite=1.8.9 (from http://P3M, type=binary, from_lockfile=false, path='') 11 | processx=3.8.4 (from http://P3M, type=binary, from_lockfile=false, path='') 12 | purrr=1.0.2 (from http://P3M, type=binary, from_lockfile=false, path='') 13 | rlang=1.1.4 (from http://P3M, type=binary, from_lockfile=false, path='') 14 | tibble=3.2.1 (from http://P3M, type=binary, from_lockfile=false, path='') 15 | whisker=0.4.1 (from http://P3M, type=binary, from_lockfile=false, path='') 16 | withr=3.0.2 (from http://P3M, type=binary, from_lockfile=false, path='') 17 | here=1.0.1 (from http://P3M, type=binary, from_lockfile=false, path='') 18 | cli=3.6.3 (from http://P3M, type=binary, from_lockfile=false, path='') 19 | stringi=1.8.4 (from http://P3M, type=binary, from_lockfile=false, path='') 20 | hms=1.1.3 (from http://P3M, type=binary, from_lockfile=false, path='') 21 | lifecycle=1.0.4 (from http://P3M, type=binary, from_lockfile=false, path='') 22 | generics=0.1.3 (from http://P3M, type=binary, from_lockfile=false, path='') 23 | magrittr=2.0.3 (from http://P3M, type=binary, from_lockfile=false, path='') 24 | pillar=1.9.0 (from http://P3M, type=binary, from_lockfile=false, path='') 25 | R6=2.5.1 (from http://P3M, type=binary, from_lockfile=false, path='') 26 | tidyselect=1.2.1 (from http://P3M, type=binary, from_lockfile=false, path='') 27 | vctrs=0.6.5 (from http://P3M, type=binary, from_lockfile=false, path='') 28 | ps=1.8.1 (from http://P3M, type=binary, from_lockfile=false, path='') 29 | fansi=1.0.6 (from http://P3M, type=binary, from_lockfile=false, path='') 30 | pkgconfig=2.0.3 (from http://P3M, type=binary, from_lockfile=false, path='') 31 | rprojroot=2.0.4 (from http://P3M, type=binary, from_lockfile=false, path='') 32 | utf8=1.2.4 (from http://P3M, type=binary, from_lockfile=false, path='') 33 | -------------------------------------------------------------------------------- /src/snapshots/rv__resolver__tests__multi-repo-combination-suggests-failure.txt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/resolver.rs 3 | expression: out 4 | --- 5 | slurmtools=0.1.2 (from http://a2-ai-universe, type=source, from_lockfile=false, path='') 6 | brio=1.1.5 (from http://P3M, type=binary, from_lockfile=false, path='') 7 | dplyr=1.1.4 (from http://P3M, type=binary, from_lockfile=false, path='') 8 | fs=1.6.5 (from http://P3M, type=binary, from_lockfile=false, path='') 9 | glue=1.8.0 (from http://P3M, type=binary, from_lockfile=false, path='') 10 | jsonlite=1.8.9 (from http://P3M, type=binary, from_lockfile=false, path='') 11 | processx=3.8.4 (from http://P3M, type=binary, from_lockfile=false, path='') 12 | purrr=1.0.2 (from http://P3M, type=binary, from_lockfile=false, path='') 13 | rlang=1.1.4 (from http://P3M, type=binary, from_lockfile=false, path='') 14 | tibble=3.2.1 (from http://P3M, type=binary, from_lockfile=false, path='') 15 | whisker=0.4.1 (from http://P3M, type=binary, from_lockfile=false, path='') 16 | withr=3.0.2 (from http://P3M, type=binary, from_lockfile=false, path='') 17 | here=1.0.1 (from http://P3M, type=binary, from_lockfile=false, path='') 18 | cli=3.6.3 (from http://P3M, type=binary, from_lockfile=false, path='') 19 | stringi=1.8.4 (from http://P3M, type=binary, from_lockfile=false, path='') 20 | hms=1.1.3 (from http://P3M, type=binary, from_lockfile=false, path='') 21 | lifecycle=1.0.4 (from http://P3M, type=binary, from_lockfile=false, path='') 22 | knitr=1.49 (from http://P3M, type=binary, from_lockfile=false, path='') 23 | rmarkdown=2.29 (from http://P3M, type=binary, from_lockfile=false, path='') 24 | testthat=3.2.2 (from http://P3M, type=binary, from_lockfile=false, path='') 25 | generics=0.1.3 (from http://P3M, type=binary, from_lockfile=false, path='') 26 | magrittr=2.0.3 (from http://P3M, type=binary, from_lockfile=false, path='') 27 | pillar=1.9.0 (from http://P3M, type=binary, from_lockfile=false, path='') 28 | R6=2.5.1 (from http://P3M, type=binary, from_lockfile=false, path='') 29 | tidyselect=1.2.1 (from http://P3M, type=binary, from_lockfile=false, path='') 30 | vctrs=0.6.5 (from http://P3M, type=binary, from_lockfile=false, path='') 31 | ps=1.8.1 (from http://P3M, type=binary, from_lockfile=false, path='') 32 | fansi=1.0.6 (from http://P3M, type=binary, from_lockfile=false, path='') 33 | pkgconfig=2.0.3 (from http://P3M, type=binary, from_lockfile=false, path='') 34 | rprojroot=2.0.4 (from http://P3M, type=binary, from_lockfile=false, path='') 35 | evaluate=1.0.1 (from http://P3M, type=binary, from_lockfile=false, path='') 36 | highr=0.11 (from http://P3M, type=binary, from_lockfile=false, path='') 37 | xfun=0.49 (from http://P3M, type=binary, from_lockfile=false, path='') 38 | yaml=2.3.10 (from http://P3M, type=binary, from_lockfile=false, path='') 39 | bslib=0.8.0 (from http://P3M, type=binary, from_lockfile=false, path='') 40 | fontawesome=0.5.3 (from http://P3M, type=binary, from_lockfile=false, path='') 41 | htmltools=0.5.8.1 (from http://P3M, type=binary, from_lockfile=false, path='') 42 | jquerylib=0.1.4 (from http://P3M, type=binary, from_lockfile=false, path='') 43 | tinytex=0.54 (from http://P3M, type=binary, from_lockfile=false, path='') 44 | callr=3.7.6 (from http://P3M, type=binary, from_lockfile=false, path='') 45 | desc=1.4.3 (from http://P3M, type=binary, from_lockfile=false, path='') 46 | digest=0.6.37 (from http://P3M, type=binary, from_lockfile=false, path='') 47 | pkgload=1.4.0 (from http://P3M, type=binary, from_lockfile=false, path='') 48 | praise=1.0.0 (from http://P3M, type=binary, from_lockfile=false, path='') 49 | waldo=0.6.1 (from http://P3M, type=binary, from_lockfile=false, path='') 50 | utf8=1.2.4 (from http://P3M, type=binary, from_lockfile=false, path='') 51 | base64enc=0.1-3 (from http://P3M, type=binary, from_lockfile=false, path='') 52 | cachem=1.1.0 (from http://P3M, type=binary, from_lockfile=false, path='') 53 | fastmap=1.2.0 (from http://P3M, type=binary, from_lockfile=false, path='') 54 | memoise=2.0.1 (from http://P3M, type=binary, from_lockfile=false, path='') 55 | mime=0.12 (from http://P3M, type=binary, from_lockfile=false, path='') 56 | sass=0.4.9 (from http://P3M, type=binary, from_lockfile=false, path='') 57 | pkgbuild=1.4.5 (from http://P3M, type=binary, from_lockfile=false, path='') 58 | diffobj=0.3.5 (from http://P3M, type=binary, from_lockfile=false, path='') 59 | rappdirs=0.3.3 (from http://P3M, type=binary, from_lockfile=false, path='') 60 | crayon=1.5.3 (from http://P3M, type=binary, from_lockfile=false, path='') 61 | --- unresolved --- 62 | bbr from: dependency of `slurmtools` 63 | -------------------------------------------------------------------------------- /src/sync/changes.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::time::Duration; 3 | 4 | use crate::lockfile::Source; 5 | use crate::package::PackageType; 6 | use crate::system_req::{SysDep, SysInstallationStatus}; 7 | use serde::{Serialize, Serializer}; 8 | 9 | fn serialize_duration_as_ms( 10 | duration: &Option, 11 | serializer: S, 12 | ) -> Result 13 | where 14 | S: Serializer, 15 | { 16 | match duration { 17 | Some(duration) => serializer.serialize_u64(duration.as_millis() as u64), 18 | None => serializer.serialize_none(), 19 | } 20 | } 21 | 22 | #[derive(Debug, Serialize)] 23 | pub struct SyncChange { 24 | pub name: String, 25 | #[serde(skip)] 26 | pub installed: bool, 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub kind: Option, 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | pub version: Option, 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub source: Option, 33 | #[serde(serialize_with = "serialize_duration_as_ms")] 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub timing: Option, 36 | #[serde(skip_serializing_if = "Vec::is_empty")] 37 | pub sys_deps: Vec, 38 | } 39 | 40 | impl SyncChange { 41 | pub fn installed( 42 | name: &str, 43 | version: &str, 44 | source: Source, 45 | kind: PackageType, 46 | timing: Duration, 47 | sys_deps: Vec, 48 | ) -> Self { 49 | Self { 50 | name: name.to_string(), 51 | installed: true, 52 | kind: Some(kind), 53 | timing: Some(timing), 54 | source: Some(source), 55 | version: Some(version.to_string()), 56 | sys_deps: sys_deps.into_iter().map(|x| SysDep::new(x)).collect(), 57 | } 58 | } 59 | 60 | pub fn removed(name: &str) -> Self { 61 | Self { 62 | name: name.to_string(), 63 | installed: false, 64 | kind: None, 65 | timing: None, 66 | source: None, 67 | version: None, 68 | sys_deps: Vec::new(), 69 | } 70 | } 71 | 72 | pub fn update_sys_deps_status( 73 | &mut self, 74 | sysdeps_status: &HashMap, 75 | ) { 76 | for sys_dep in &mut self.sys_deps { 77 | if let Some(status) = sysdeps_status.get(&sys_dep.name) { 78 | sys_dep.status = status.clone(); 79 | } 80 | } 81 | } 82 | 83 | pub fn print(&self, include_timings: bool, supports_sysdeps_status: bool) -> String { 84 | if self.installed { 85 | let sys_deps = { 86 | let mut out = Vec::new(); 87 | for sys_dep in &self.sys_deps { 88 | let status = if !supports_sysdeps_status { 89 | String::new() 90 | } else { 91 | format!( 92 | "{} ", 93 | if sys_dep.status == SysInstallationStatus::Present { 94 | "✓" 95 | } else { 96 | "✗" 97 | } 98 | ) 99 | }; 100 | out.push(format!("{status}{}", sys_dep.name)) 101 | } 102 | out 103 | }; 104 | let mut base = format!( 105 | "+ {} ({}, {} from {}){}", 106 | self.name, 107 | self.version.as_ref().unwrap(), 108 | self.kind.unwrap(), 109 | self.source.as_ref().map(|x| x.to_string()).unwrap(), 110 | if sys_deps.is_empty() { 111 | String::new() 112 | } else { 113 | format!(" with sys deps: {}", sys_deps.join(", ")) 114 | } 115 | ); 116 | 117 | if include_timings { 118 | base += &format!(" in {}ms", self.timing.unwrap().as_millis()); 119 | base 120 | } else { 121 | base 122 | } 123 | } else { 124 | format!("- {}", self.name) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/sync/errors.rs: -------------------------------------------------------------------------------- 1 | use crate::http::HttpError; 2 | use crate::r_cmd::InstallError; 3 | use crate::sync::LinkError; 4 | use std::fmt; 5 | use std::fmt::Formatter; 6 | use std::io; 7 | 8 | #[derive(Debug, thiserror::Error)] 9 | #[error(transparent)] 10 | #[non_exhaustive] 11 | pub struct SyncError { 12 | pub source: SyncErrorKind, 13 | } 14 | 15 | #[derive(Debug, thiserror::Error)] 16 | pub enum SyncErrorKind { 17 | #[error(transparent)] 18 | Io(#[from] io::Error), 19 | #[error("Failed to link files from cache: {0:?})")] 20 | LinkError(LinkError), 21 | #[error("Failed to install R package: {0})")] 22 | InstallError(InstallError), 23 | #[error("Failed to download package: {0:?})")] 24 | HttpError(HttpError), 25 | #[error("{0}")] 26 | SyncFailed(SyncErrors), 27 | } 28 | 29 | impl From for SyncError { 30 | fn from(error: InstallError) -> Self { 31 | Self { 32 | source: SyncErrorKind::InstallError(error), 33 | } 34 | } 35 | } 36 | 37 | impl From for SyncError { 38 | fn from(error: LinkError) -> Self { 39 | Self { 40 | source: SyncErrorKind::LinkError(error), 41 | } 42 | } 43 | } 44 | 45 | impl From for SyncError { 46 | fn from(error: HttpError) -> Self { 47 | Self { 48 | source: SyncErrorKind::HttpError(error), 49 | } 50 | } 51 | } 52 | 53 | impl From for SyncError { 54 | fn from(error: io::Error) -> Self { 55 | Self { 56 | source: SyncErrorKind::Io(error), 57 | } 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct SyncErrors { 63 | pub(crate) errors: Vec<(String, SyncError)>, 64 | } 65 | 66 | impl fmt::Display for SyncErrors { 67 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 68 | write!(f, "Failed to install dependencies.")?; 69 | 70 | for (dep, e) in &self.errors { 71 | write!(f, "\n Failed to install {dep}:\n {e}")?; 72 | } 73 | 74 | Ok(()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/sync/mod.rs: -------------------------------------------------------------------------------- 1 | mod build_plan; 2 | mod changes; 3 | mod errors; 4 | mod handler; 5 | mod link; 6 | mod sources; 7 | 8 | pub use build_plan::{BuildPlan, BuildStep}; 9 | pub use changes::SyncChange; 10 | pub use handler::SyncHandler; 11 | pub use link::{LinkError, LinkMode}; 12 | -------------------------------------------------------------------------------- /src/sync/sources/git.rs: -------------------------------------------------------------------------------- 1 | use crate::git::{GitReference, GitRemote}; 2 | use crate::library::LocalMetadata; 3 | use crate::lockfile::Source; 4 | use crate::sync::LinkMode; 5 | use crate::sync::errors::SyncError; 6 | use crate::{CommandExecutor, DiskCache, RCmd, ResolvedDependency}; 7 | use std::path::Path; 8 | 9 | pub(crate) fn install_package( 10 | pkg: &ResolvedDependency, 11 | library_dir: &Path, 12 | cache: &DiskCache, 13 | r_cmd: &impl RCmd, 14 | git_exec: &(impl CommandExecutor + Clone + 'static), 15 | ) -> Result<(), SyncError> { 16 | let pkg_paths = cache.get_package_paths(&pkg.source, None, None); 17 | 18 | // We will have the source version since we needed to clone it to get the DESCRIPTION file 19 | if !pkg.installation_status.binary_available() { 20 | let (repo_url, sha) = match &pkg.source { 21 | Source::Git { git, sha, .. } => (git.url(), sha), 22 | Source::RUniverse { git, sha, .. } => (git.url(), sha), 23 | _ => unreachable!(), 24 | }; 25 | 26 | // TODO: this won't work if multiple projects are trying to checkout different refs 27 | // on the same user at the same time 28 | let remote = GitRemote::new(repo_url); 29 | remote.checkout( 30 | &pkg_paths.source, 31 | &GitReference::Commit(sha), 32 | git_exec.clone(), 33 | )?; 34 | // If we have a directory, don't forget to set it before building it 35 | let source_path = match &pkg.source { 36 | Source::Git { 37 | directory: Some(dir), 38 | .. 39 | } 40 | | Source::RUniverse { 41 | directory: Some(dir), 42 | .. 43 | } => pkg_paths.source.join(dir), 44 | _ => pkg_paths.source, 45 | }; 46 | 47 | r_cmd.install(&source_path, library_dir, &pkg_paths.binary)?; 48 | let metadata = LocalMetadata::Sha(sha.to_owned()); 49 | metadata.write(pkg_paths.binary.join(pkg.name.as_ref()))?; 50 | } 51 | 52 | // And then we always link the binary folder into the staging library 53 | LinkMode::new().link_files(&pkg.name, &pkg_paths.binary, library_dir)?; 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /src/sync/sources/local.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use fs_err as fs; 4 | 5 | use crate::fs::{mtime_recursive, untar_archive}; 6 | use crate::library::LocalMetadata; 7 | use crate::lockfile::Source; 8 | use crate::sync::LinkMode; 9 | use crate::sync::errors::SyncError; 10 | use crate::{RCmd, ResolvedDependency, is_binary_package}; 11 | 12 | pub(crate) fn install_package( 13 | pkg: &ResolvedDependency, 14 | project_dir: &Path, 15 | library_dir: &Path, 16 | r_cmd: &impl RCmd, 17 | ) -> Result<(), SyncError> { 18 | let (local_path, sha) = match &pkg.source { 19 | Source::Local { path, sha } => (path, sha.clone()), 20 | _ => unreachable!(), 21 | }; 22 | 23 | let tempdir = tempfile::tempdir()?; 24 | let canon_path = fs::canonicalize(project_dir.join(local_path))?; 25 | 26 | let actual_path = if canon_path.is_file() { 27 | // TODO: we're already untarring in resolve, that's wasteful 28 | let (path, _) = untar_archive(fs::read(&canon_path)?.as_slice(), tempdir.path(), false)?; 29 | path.unwrap_or_else(|| canon_path.clone()) 30 | } else { 31 | canon_path.clone() 32 | }; 33 | 34 | if is_binary_package(&actual_path, pkg.name.as_ref()) { 35 | log::debug!( 36 | "Local package in {} is a binary package, copying files to library.", 37 | actual_path.display() 38 | ); 39 | LinkMode::Copy.link_files( 40 | pkg.name.as_ref(), 41 | &actual_path, 42 | library_dir.join(pkg.name.as_ref()), 43 | )?; 44 | } else { 45 | log::debug!("Building the local package in {}", actual_path.display()); 46 | r_cmd.install(&actual_path, library_dir, library_dir)?; 47 | } 48 | 49 | // If it's a dir, save the dir mtime and if it's a tarball its sha 50 | let metadata = if canon_path.is_dir() { 51 | let local_mtime = mtime_recursive(&actual_path)?; 52 | LocalMetadata::Mtime(local_mtime.unix_seconds()) 53 | } else { 54 | LocalMetadata::Sha(sha.unwrap()) 55 | }; 56 | metadata.write(library_dir.join(pkg.name.as_ref()))?; 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /src/sync/sources/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod git; 2 | pub(crate) mod local; 3 | pub(crate) mod repositories; 4 | pub(crate) mod url; 5 | 6 | // TODO: verify local/url since they feel weird 7 | -------------------------------------------------------------------------------- /src/sync/sources/repositories.rs: -------------------------------------------------------------------------------- 1 | //! Download and install packages from repositories like CRAN, posit etc 2 | 3 | use std::path::Path; 4 | 5 | use fs_err as fs; 6 | 7 | use crate::cache::InstallationStatus; 8 | use crate::http::Http; 9 | use crate::package::PackageType; 10 | use crate::sync::LinkMode; 11 | use crate::sync::errors::SyncError; 12 | use crate::{ 13 | DiskCache, HttpDownload, RCmd, ResolvedDependency, get_tarball_urls, is_binary_package, 14 | }; 15 | 16 | pub(crate) fn install_package( 17 | pkg: &ResolvedDependency, 18 | library_dir: &Path, 19 | cache: &DiskCache, 20 | r_cmd: &impl RCmd, 21 | ) -> Result<(), SyncError> { 22 | let pkg_paths = 23 | cache.get_package_paths(&pkg.source, Some(&pkg.name), Some(&pkg.version.original)); 24 | let compile_package = || { 25 | let source_path = pkg_paths.source.join(pkg.name.as_ref()); 26 | log::debug!("Compiling package from {}", source_path.display()); 27 | r_cmd.install(&source_path, library_dir, &pkg_paths.binary) 28 | }; 29 | 30 | match pkg.installation_status { 31 | InstallationStatus::Source => { 32 | log::debug!( 33 | "Package {} ({}) already present in cache as source but not as binary.", 34 | pkg.name, 35 | pkg.version.original 36 | ); 37 | compile_package()?; 38 | } 39 | InstallationStatus::Absent => { 40 | log::debug!( 41 | "Package {} ({}) not found in cache, trying to download it.", 42 | pkg.name, 43 | pkg.version.original 44 | ); 45 | 46 | let tarball_url = get_tarball_urls(pkg, &cache.r_version, &cache.system_info) 47 | .expect("Dependency has source Repository"); 48 | let http = Http {}; 49 | 50 | let download_and_install_source_or_archive = || -> Result<(), SyncError> { 51 | log::debug!( 52 | "Downloading package {} ({}) as source tarball", 53 | pkg.name, 54 | pkg.version.original 55 | ); 56 | if let Err(e) = 57 | http.download_and_untar(&tarball_url.source, &pkg_paths.source, false) 58 | { 59 | log::warn!( 60 | "Failed to download/untar source package from {}: {e:?}, falling back to {}", 61 | tarball_url.source, 62 | tarball_url.archive 63 | ); 64 | log::debug!( 65 | "Downloading package {} ({}) from archive", 66 | pkg.name, 67 | pkg.version.original 68 | ); 69 | http.download_and_untar(&tarball_url.archive, &pkg_paths.source, false)?; 70 | } 71 | compile_package()?; 72 | Ok(()) 73 | }; 74 | 75 | if pkg.kind == PackageType::Source || tarball_url.binary.is_none() { 76 | download_and_install_source_or_archive()?; 77 | } else { 78 | // If we get an error doing the binary download, fall back to source 79 | if let Err(e) = http.download_and_untar( 80 | &tarball_url.binary.clone().unwrap(), 81 | &pkg_paths.binary, 82 | false, 83 | ) { 84 | log::warn!( 85 | "Failed to download/untar binary package from {}: {e:?}, falling back to {}", 86 | tarball_url.binary.clone().unwrap(), 87 | tarball_url.source 88 | ); 89 | download_and_install_source_or_archive()?; 90 | } else { 91 | // Ok we download some tarball. We can't assume it's actually compiled though, it could be just 92 | // source files. We have to check first whether what we have is actually binary content. 93 | if !is_binary_package( 94 | pkg_paths.binary.join(pkg.name.as_ref()), 95 | pkg.name.as_ref(), 96 | ) { 97 | log::debug!("{} was expected as binary, found to be source.", pkg.name); 98 | // Move it to the source destination if we don't have it already 99 | if pkg_paths.source.is_dir() { 100 | fs::remove_dir_all(&pkg_paths.binary)?; 101 | } else { 102 | fs::create_dir_all(&pkg_paths.source)?; 103 | fs::rename(&pkg_paths.binary, &pkg_paths.source)?; 104 | } 105 | compile_package()?; 106 | } 107 | } 108 | } 109 | } 110 | _ => {} 111 | } 112 | // And then we always link the binary folder into the staging library 113 | LinkMode::new().link_files(&pkg.name, &pkg_paths.binary, library_dir)?; 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /src/sync/sources/url.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use crate::library::LocalMetadata; 4 | use crate::package::PackageType; 5 | use crate::sync::LinkMode; 6 | use crate::sync::errors::SyncError; 7 | use crate::{DiskCache, RCmd, ResolvedDependency}; 8 | 9 | pub(crate) fn install_package( 10 | pkg: &ResolvedDependency, 11 | library_dir: &Path, 12 | cache: &DiskCache, 13 | r_cmd: &impl RCmd, 14 | ) -> Result<(), SyncError> { 15 | let pkg_paths = cache.get_package_paths(&pkg.source, None, None); 16 | let download_path = pkg_paths.source.join(pkg.name.as_ref()); 17 | 18 | // If we have a binary, copy it since we don't keep cache around for binary URL packages 19 | if pkg.kind == PackageType::Binary { 20 | log::debug!( 21 | "Package from URL in {} is already a binary", 22 | download_path.display() 23 | ); 24 | if !pkg_paths.binary.is_dir() { 25 | LinkMode::Copy.link_files(&pkg.name, &pkg_paths.source, &pkg_paths.binary)?; 26 | } 27 | } else { 28 | log::debug!( 29 | "Building the package from URL in {}", 30 | download_path.display() 31 | ); 32 | r_cmd.install(&download_path, library_dir, &pkg_paths.binary)?; 33 | } 34 | 35 | let metadata = LocalMetadata::Sha(pkg.source.sha().to_owned()); 36 | metadata.write(pkg_paths.binary.join(pkg.name.as_ref()))?; 37 | 38 | // And then we always link the binary folder into the staging library 39 | LinkMode::new().link_files(&pkg.name, &pkg_paths.binary, library_dir)?; 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /src/system_info.rs: -------------------------------------------------------------------------------- 1 | //! For R we will need some information on what is the current OS. 2 | //! We can get that information from the `os_info` crate but we don't want to expose its type 3 | //! to the library/CLI. 4 | //! Instead, we encode the data we care about in an enum that can easily be shared 5 | 6 | use os_info::{Type, Version}; 7 | use serde::Serialize; 8 | use std::fmt; 9 | 10 | /// For R we only care about Windows, MacOS and Linux 11 | #[derive(Debug, PartialEq, Clone, Copy, Serialize)] 12 | pub enum OsType { 13 | Windows, 14 | MacOs, 15 | Linux(&'static str), 16 | // TODO: we should error before we get that and remove that variant 17 | Other(Type), 18 | } 19 | 20 | impl OsType { 21 | pub fn family(&self) -> &'static str { 22 | match self { 23 | OsType::Windows => "windows", 24 | OsType::MacOs => "macos", 25 | OsType::Linux(_) => "linux", 26 | OsType::Other(_) => "other", 27 | } 28 | } 29 | 30 | pub fn tarball_extension(&self) -> &'static str { 31 | match self { 32 | OsType::Windows => "zip", 33 | OsType::MacOs => "tgz", 34 | OsType::Linux(_) | OsType::Other(_) => "tar.gz", 35 | } 36 | } 37 | } 38 | 39 | fn serialize_display(value: &T, serializer: S) -> Result 40 | where 41 | T: fmt::Display, 42 | S: serde::Serializer, 43 | { 44 | serializer.collect_str(value) 45 | } 46 | 47 | #[derive(Debug, PartialEq, Clone, Serialize)] 48 | pub struct SystemInfo { 49 | pub os_type: OsType, 50 | // AFAIK we need that for ubuntu distrib name for posit binaries 51 | codename: Option, 52 | // AFAIK we need that for mac os version name (eg big sur etc) for CRAN urls 53 | #[serde(serialize_with = "serialize_display")] 54 | pub version: Version, 55 | arch: Option, 56 | } 57 | 58 | impl SystemInfo { 59 | pub fn new( 60 | os_type: OsType, 61 | arch: Option, 62 | codename: Option, 63 | version: &str, 64 | ) -> Self { 65 | Self { 66 | os_type, 67 | arch, 68 | codename, 69 | version: Version::Custom(version.to_string()), 70 | } 71 | } 72 | 73 | pub fn from_os_info() -> Self { 74 | let info = os_info::get(); 75 | let os_type = match info.os_type() { 76 | Type::Windows => OsType::Windows, 77 | // TODO: https://github.com/stanislav-tkach/os_info/pull/313 78 | // In the meantime, we do it manually for the main distribs and can add more as needed 79 | Type::Linux => OsType::Linux(""), 80 | Type::Ubuntu => OsType::Linux("ubuntu"), 81 | Type::Fedora => OsType::Linux("fedora"), 82 | Type::Arch => OsType::Linux("arch"), 83 | Type::Amazon => OsType::Linux("amazon"), 84 | Type::Debian => OsType::Linux("debian"), 85 | Type::Pop => OsType::Linux("pop"), 86 | Type::CentOS => OsType::Linux("centos"), 87 | Type::openSUSE => OsType::Linux("opensuse"), 88 | Type::Redhat => OsType::Linux("redhat"), 89 | Type::RockyLinux => OsType::Linux("rocky"), 90 | Type::SUSE => OsType::Linux("suse"), 91 | Type::Macos => OsType::MacOs, 92 | _ => OsType::Other(info.os_type()), 93 | }; 94 | 95 | Self { 96 | os_type, 97 | codename: info.codename().map(|s| s.to_string()), 98 | arch: info.architecture().map(|s| s.to_string()), 99 | version: info.version().clone(), 100 | } 101 | } 102 | 103 | pub fn os_family(&self) -> &'static str { 104 | self.os_type.family() 105 | } 106 | 107 | pub fn codename(&self) -> Option<&str> { 108 | self.codename.as_deref() 109 | } 110 | 111 | pub fn arch(&self) -> Option<&str> { 112 | self.arch.as_deref() 113 | } 114 | 115 | /// Returns (distrib name, version) 116 | pub fn sysreq_data(&self) -> (&'static str, String) { 117 | match self.os_type { 118 | OsType::Linux(distrib) => match distrib { 119 | "suse" => ("sle", self.version.to_string()), 120 | "ubuntu" => { 121 | let version = match self.version { 122 | Version::Semantic(year, month, _) => { 123 | format!("{year}.{}{month}", if month < 10 { "0" } else { "" }) 124 | } 125 | _ => unreachable!(), 126 | }; 127 | (distrib, version) 128 | } 129 | _ => (distrib, self.version.to_string()), 130 | }, 131 | _ => ("invalid", String::new()), 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/system_req.rs: -------------------------------------------------------------------------------- 1 | use crate::{SystemInfo, http}; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::fmt; 4 | use std::fmt::Formatter; 5 | use std::process::Command; 6 | 7 | use crate::consts::SYS_REQ_URL_ENV_VAR_NAME; 8 | use serde::{Deserialize, Serialize}; 9 | use url::Url; 10 | 11 | /// https://rserver.tradecraftclinical.com/rspm/__api__/swagger/index.html#/default/get_repos__id__sysreqs 12 | const SYSTEM_REQ_API_URL: &str = "https://packagemanager.posit.co/__api__/repos/cran/sysreqs"; 13 | 14 | #[derive(Serialize, Clone, Debug, PartialEq)] 15 | #[serde(rename_all = "lowercase")] 16 | pub enum SysInstallationStatus { 17 | Present, 18 | Absent, 19 | Unknown, 20 | } 21 | 22 | impl fmt::Display for SysInstallationStatus { 23 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 24 | match self { 25 | Self::Present => write!(f, "present"), 26 | Self::Absent => write!(f, "absent"), 27 | Self::Unknown => write!(f, "unknown"), 28 | } 29 | } 30 | } 31 | 32 | #[derive(Debug, Clone, Serialize)] 33 | pub struct SysDep { 34 | pub name: String, 35 | pub status: SysInstallationStatus, 36 | } 37 | 38 | impl SysDep { 39 | pub fn new(name: String) -> Self { 40 | Self { 41 | name, 42 | status: SysInstallationStatus::Unknown, 43 | } 44 | } 45 | } 46 | 47 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 48 | struct Requirements { 49 | packages: Vec, 50 | } 51 | 52 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 53 | struct Package { 54 | name: String, 55 | requirements: Requirements, 56 | } 57 | 58 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 59 | struct Response { 60 | requirements: Vec, 61 | } 62 | 63 | fn get_sysreq_url() -> String { 64 | std::env::var(SYS_REQ_URL_ENV_VAR_NAME).unwrap_or_else(|_| SYSTEM_REQ_API_URL.to_string()) 65 | } 66 | 67 | pub fn is_supported(system_info: &SystemInfo) -> bool { 68 | let (distrib, version) = system_info.sysreq_data(); 69 | 70 | match distrib { 71 | "ubuntu" => ["20.04", "22.04", "24.04"].contains(&version.as_str()), 72 | "debian" => version.starts_with("12"), 73 | "centos" => version.starts_with("7") || version.starts_with("8"), 74 | "redhat" => { 75 | version.starts_with("7") || version.starts_with("8") || version.starts_with("9") 76 | } 77 | "rockylinux" => version.starts_with("9"), 78 | "opensuse" | "sle" => version.starts_with("15"), 79 | _ => false, 80 | } 81 | } 82 | 83 | /// This should only be run on Linux 84 | pub fn get_system_requirements(system_info: &SystemInfo) -> HashMap> { 85 | let agent = http::get_agent(); 86 | let mut url = Url::parse(&get_sysreq_url()).unwrap(); 87 | 88 | { 89 | let mut pairs = url.query_pairs_mut(); 90 | pairs.append_pair("all", "true"); 91 | // pairs.append_pair("distribution", "ubuntu"); 92 | // pairs.append_pair("release", "22.04"); 93 | let (distrib, version) = system_info.sysreq_data(); 94 | pairs.append_pair("distribution", distrib); 95 | pairs.append_pair("release", version.as_str()); 96 | } 97 | 98 | log::debug!("Getting sysreq data from {}", url.as_str()); 99 | 100 | let response = agent 101 | .get(url.as_str()) 102 | .header("Accept", "application/json") 103 | .call() 104 | .unwrap() 105 | .body_mut() 106 | .read_json::() 107 | .unwrap(); 108 | 109 | let mut out = HashMap::new(); 110 | for package in response.requirements { 111 | out.insert(package.name, package.requirements.packages); 112 | } 113 | 114 | out 115 | } 116 | 117 | pub fn check_installation_status( 118 | system_info: &SystemInfo, 119 | sys_deps: &HashSet<&str>, 120 | ) -> HashMap { 121 | if !is_supported(system_info) { 122 | return HashMap::new(); 123 | } 124 | 125 | let mut out = HashMap::from_iter( 126 | sys_deps 127 | .iter() 128 | .map(|x| (x.to_string(), SysInstallationStatus::Unknown)), 129 | ); 130 | if sys_deps.is_empty() { 131 | return out; 132 | } 133 | 134 | log::debug!("Checking installation status for {:?}", sys_deps); 135 | 136 | match system_info.sysreq_data().0 { 137 | "ubuntu" | "debian" => { 138 | // Running dpkg-query -W -f='${Package}\n' {..pkg_list} and read stdout 139 | let command = Command::new("dpkg-query") 140 | .arg("-W") 141 | .arg("-f=${Package}\n") 142 | .args(sys_deps) 143 | .output() 144 | .expect("to be able to run commands"); 145 | 146 | let stdout = String::from_utf8(command.stdout).unwrap(); 147 | for line in stdout.lines() { 148 | if let Some(status) = out.get_mut(line.trim()) { 149 | *status = SysInstallationStatus::Present; 150 | } 151 | } 152 | } 153 | 154 | // "debian" => version.starts_with("12"), 155 | // "centos" => version.starts_with("7") || version.starts_with("8"), 156 | // "redhat" => { 157 | // version.starts_with("7") || version.starts_with("8") || version.starts_with("9") 158 | // } 159 | // "rockylinux" => version.starts_with("9"), 160 | // "opensuse" | "sle" => version.starts_with("15"), 161 | _ => (), 162 | }; 163 | 164 | for (_, status) in out 165 | .iter_mut() 166 | .filter(|(_, x)| **x == SysInstallationStatus::Unknown) 167 | { 168 | *status = SysInstallationStatus::Absent; 169 | } 170 | 171 | out 172 | } 173 | -------------------------------------------------------------------------------- /src/tests/descriptions/clindata.DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: clindata 2 | Title: Synthetic Clinical Data for testing and development 3 | Version: 1.0.5 4 | Authors@R:c( 5 | person("Jeremy", "Wildfire", email = "jwildfire@gmail.com", role = c("aut", "cre")), 6 | person("Spencer", "Childress", email = "spencer.childress@gilead.com", role = c("aut"), comment = c(ORCID = "0000-0001-6877-8511")), 7 | person("Wang", "Zhongkai", email="zhongkai.wang6@gilead.com", role = c("aut"), comment = c(ORCID = "0000-0002-1883-2265")), 8 | person("Matt", "Roumaya", email = "Matt.Roumaya@atorusresearch.com", role = c("aut"), comment = c(ORCID = "0000-0002-8009-2771")), 9 | person("Colleen", "McLaughlin", email="colleen.mclaughlin@atorusresearch.com", role = c("aut")), 10 | person("Chelsea", "Dickens", email="chelsea.dickens@atorusresearch.com", role = c("aut")), 11 | person("Douglas", "Sanders", role = c("aut")), 12 | person("Jacob", "Anderson", email="jacob.anderson@atorusresearch.com", role = c("aut")), 13 | person(given = "Gilead Sciences", role = "cph") 14 | ) 15 | Description: Synthetic Clinical Data for testing and development. Raw form data to start, more data formats in future releases. 16 | License: Apache License (>= 2) 17 | Encoding: UTF-8 18 | LazyData: true 19 | LazyDataCompression: xz 20 | Roxygen: list(markdown = TRUE) 21 | RoxygenNote: 7.2.3 22 | Imports: 23 | arrow, 24 | cli, 25 | data.table, 26 | dplyr, 27 | glue, 28 | lubridate, 29 | magrittr, 30 | purrr, 31 | readr, 32 | rlang, 33 | stringr, 34 | tibble, 35 | tidyr, 36 | yaml 37 | Suggests: 38 | testthat (>= 3.0.0), 39 | gsm, 40 | gh, 41 | here, 42 | remotes, 43 | tictoc, 44 | usethis 45 | Depends: 46 | R (>= 3.5) 47 | URL: https://gilead-biostats.github.io/clindata/ 48 | -------------------------------------------------------------------------------- /src/tests/descriptions/dplyr.DESCRIPTION: -------------------------------------------------------------------------------- 1 | Type: Package 2 | Package: dplyr 3 | Title: A Grammar of Data Manipulation 4 | Version: 1.1.3 5 | Authors@R: c( 6 | person("Hadley", "Wickham", , "hadley@posit.co", role = c("aut", "cre"), 7 | comment = c(ORCID = "0000-0003-4757-117X")), 8 | person("Romain", "François", role = "aut", 9 | comment = c(ORCID = "0000-0002-2444-4226")), 10 | person("Lionel", "Henry", role = "aut"), 11 | person("Kirill", "Müller", role = "aut", 12 | comment = c(ORCID = "0000-0002-1416-3412")), 13 | person("Davis", "Vaughan", , "davis@posit.co", role = "aut", 14 | comment = c(ORCID = "0000-0003-4777-038X")), 15 | person("Posit Software, PBC", role = c("cph", "fnd")) 16 | ) 17 | Description: A fast, consistent tool for working with data frame like 18 | objects, both in memory and out of memory. 19 | License: MIT + file LICENSE 20 | URL: https://dplyr.tidyverse.org, https://github.com/tidyverse/dplyr 21 | BugReports: https://github.com/tidyverse/dplyr/issues 22 | Depends: R (>= 3.5.0) 23 | Imports: cli (>= 3.4.0), generics, glue (>= 1.3.2), lifecycle (>= 24 | 1.0.3), magrittr (>= 1.5), methods, pillar (>= 1.9.0), R6, 25 | rlang (>= 1.1.0), tibble (>= 3.2.0), tidyselect (>= 1.2.0), 26 | utils, vctrs (>= 0.6.0) 27 | Suggests: bench, broom, callr, covr, DBI, dbplyr (>= 2.2.1), ggplot2, 28 | knitr, Lahman, lobstr, microbenchmark, nycflights13, purrr, 29 | rmarkdown, RMySQL, RPostgreSQL, RSQLite, stringi (>= 1.7.6), 30 | testthat (>= 3.1.5), tidyr (>= 1.3.0), withr 31 | VignetteBuilder: knitr 32 | Config/Needs/website: tidyverse, shiny, pkgdown, tidyverse/tidytemplate 33 | Config/testthat/edition: 3 34 | Encoding: UTF-8 35 | LazyData: true 36 | RoxygenNote: 7.2.3 37 | NeedsCompilation: yes 38 | Packaged: 2023-08-25 22:28:32 UTC; hadleywickham 39 | Author: Hadley Wickham [aut, cre] (), 40 | Romain François [aut] (), 41 | Lionel Henry [aut], 42 | Kirill Müller [aut] (), 43 | Davis Vaughan [aut] (), 44 | Posit Software, PBC [cph, fnd] 45 | Maintainer: Hadley Wickham 46 | Repository: CRAN 47 | Date/Publication: 2023-09-03 16:20:02 UTC -------------------------------------------------------------------------------- /src/tests/descriptions/gsm.DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: gsm 2 | Title: Good Statistical Monitoring 3 | Version: 2.2.2 4 | Authors@R: c( 5 | person("George", "Wu", , "george.wu@gilead.com", role = c("aut", "cre")), 6 | person("Jeremy", "Wildfire", , "jwildfire@gmail.com", role = "aut"), 7 | person("Matt", "Roumaya", , "matthewroumaya@gmail.com", role = "aut", 8 | comment = c(ORCID = "0000-0002-8009-2771")), 9 | person("Spencer", "Childress", , "spencer.childress@gilead.com", role = "aut", 10 | comment = c(ORCID = "0000-0001-6877-8511")), 11 | person("Laura", "Maxwell", , "laura.maxwell@atorusresearch.com", role = "aut"), 12 | person("Jacob", "Anderson", , "jacob.anderson@atorusresearch.com", role = "aut"), 13 | person("Jon", "Harmon", , "jon.harmon@atorusresearch.com", role = "aut"), 14 | person("Zelos", "Zhu", , "zelos.zhu@atorusresearch.com", role = "aut"), 15 | person("Nathan", "Kosiba", , "Nathan.Kosiba@atorusresearch.com", role = "aut"), 16 | person("Douglas", "Sanders", role = "aut"), 17 | person("Li", "Ge", role = "aut"), 18 | person("Zhongkai", "Wang", , "zhongkai.wang6@gilead.com", role = "aut", 19 | comment = c(ORCID = "0000-0002-1883-2265")), 20 | person("Colleen", "McLaughlin", , "colleen.mclaughlin@atorusresearch.com", role = "aut"), 21 | person("Chelsea", "Dickens", , "chelsea.dickens@atorusresearch.com", role = "aut"), 22 | person("Maya", "Gans", , "maya.gans@atorusresearch.com", role = "aut"), 23 | person("Anne", "Zheng", , "anne@a2-ai.com", role = "aut"), 24 | person("Devin", "Pastoor", , "devin@a2-ai.com", role = "aut"), 25 | person("Gilead Sciences", role = "cph") 26 | ) 27 | Description: The Good Statistical Monitoring or 'gsm' R package provides a 28 | framework for statistical data monitoring. 29 | License: Apache License (>= 2) 30 | URL: https://github.com/Gilead-BioStats/gsm, https://gilead-biostats.github.io/gsm 31 | BugReports: https://github.com/Gilead-BioStats/gsm/issues 32 | Depends: 33 | R (>= 4.0) 34 | Imports: 35 | broom, 36 | cli, 37 | DBI, 38 | dbplyr, 39 | dplyr, 40 | ggplot2, 41 | glue, 42 | gt, 43 | htmltools, 44 | htmlwidgets, 45 | jsonlite, 46 | lifecycle, 47 | log4r, 48 | magrittr, 49 | purrr, 50 | rlang (>= 0.4.11), 51 | stats, 52 | stringr, 53 | tibble, 54 | tidyr, 55 | utils, 56 | yaml 57 | Suggests: 58 | clindata, 59 | covr, 60 | DT, 61 | duckdb, 62 | fontawesome (>= 0.2.2), 63 | gggenes, 64 | ggiraph, 65 | here, 66 | knitr, 67 | lamW, 68 | lubridate, 69 | pander, 70 | pkgdown, 71 | riskmetric, 72 | rmarkdown, 73 | roxygen2, 74 | safetyData, 75 | styler, 76 | testthat, 77 | usethis, 78 | withr 79 | VignetteBuilder: 80 | knitr 81 | Remotes: 82 | clindata=Gilead-BioStats/clindata 83 | Config/testthat/edition: 3 84 | Config/testthat/parallel: true 85 | Config/testthat/start-first: util-RunWorkflow, util-clindata 86 | Encoding: UTF-8 87 | Language: en-US 88 | LazyData: true 89 | Roxygen: list(markdown = TRUE) 90 | RoxygenNote: 7.3.2 -------------------------------------------------------------------------------- /src/tests/descriptions/gsm.app.DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: gsm.app 2 | Title: gsm Shiny Application 3 | Version: 2.3.0.9000 4 | Authors@R: c( 5 | person("Jon", "Harmon", , "jonthegeek@gmail.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0003-4781-4346")), 7 | person("Spencer", "Childress", , "spencer.childress@gilead.com", role = "aut"), 8 | person("Taylor", "Rodgers", , "Taylor.Rodgers@freestateanalytics.com", role = "aut"), 9 | person("Matt", "Roumaya", , "matthewroumaya@gmail.com", role = "aut", 10 | comment = c(ORCID = "0000-0002-8009-2771")) 11 | ) 12 | Description: Explore the results of gsm assessments. 13 | License: Apache License (>= 2) 14 | URL: https://gilead-biostats.github.io/gsm.app/, 15 | https://github.com/Gilead-BioStats/gsm.app 16 | BugReports: https://github.com/Gilead-BioStats/gsm.app/issues 17 | Depends: 18 | R (>= 4.0) 19 | Imports: 20 | bslib, 21 | cli, 22 | dplyr, 23 | favawesome, 24 | glue, 25 | gsm (>= 2.2.2), 26 | htmltools, 27 | htmlwidgets, 28 | jsonlite, 29 | magrittr, 30 | purrr, 31 | rlang, 32 | shiny (>= 1.6.0), 33 | shinyjs, 34 | yaml 35 | Suggests: 36 | chromote (>= 0.3.1), 37 | devtools, 38 | here, 39 | knitr, 40 | pak, 41 | rmarkdown, 42 | shinytest2, 43 | stringr, 44 | testthat (>= 3.0.0), 45 | usethis, 46 | withr 47 | Remotes: 48 | gsm=gilead-biostats/gsm@v2.2.2 49 | Config/testthat/edition: 3 50 | Encoding: UTF-8 51 | Language: en-US 52 | LazyData: true 53 | Roxygen: list(markdown = TRUE) 54 | RoxygenNote: 7.3.2 55 | VignetteBuilder: knitr 56 | -------------------------------------------------------------------------------- /src/tests/descriptions/missing.remote.DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: missing.remote 2 | Title: a package missing remote 3 | Version: 1.0.0 4 | Description: N/A 5 | License: Apache License (>= 2) 6 | Depends: 7 | R (>= 4.0) 8 | Imports: 9 | bslib, 10 | cli, 11 | dplyr, 12 | favawesome, 13 | glue, 14 | gsm (>= 2.2.2), 15 | htmltools, 16 | htmlwidgets, 17 | jsonlite, 18 | magrittr, 19 | purrr, 20 | rlang, 21 | shiny (>= 1.6.0), 22 | shinyjs, 23 | yaml 24 | Suggests: 25 | chromote (>= 0.3.1), 26 | devtools, 27 | here, 28 | knitr, 29 | pak, 30 | rmarkdown, 31 | shinytest2, 32 | stringr, 33 | testthat (>= 3.0.0), 34 | usethis, 35 | withr 36 | Config/testthat/edition: 3 37 | Encoding: UTF-8 38 | Language: en-US 39 | LazyData: true 40 | Roxygen: list(markdown = TRUE) 41 | RoxygenNote: 7.3.2 42 | VignetteBuilder: knitr 43 | -------------------------------------------------------------------------------- /src/tests/descriptions/shinytest2.DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: shinytest2 2 | Title: Testing for Shiny Applications 3 | Version: 0.3.2.9000 4 | Authors@R: 5 | c( 6 | person("Barret", "Schloerke", role = c("cre", "aut"), email = "barret@posit.co", comment = c(ORCID = "0000-0001-9986-114X")), 7 | person(family = "Posit Software, PBC", role = c("cph", "fnd")), 8 | person("Winston", "Chang", role ="ctb", email = "winston@posit.co", comment = "Original author to rstudio/shinytest"), 9 | person("Gábor", "Csárdi", role = "ctb", email = "gabor@posit.co", comment = "Original author to rstudio/shinytest"), 10 | person("Hadley", "Wickham", role = "ctb", email = "hadley@posit.co", comment = "Original author to rstudio/shinytest"), 11 | person(family = "Mango Solutions", role = c("cph", "ccp"), comment = "Original author to rstudio/shinytest") 12 | ) 13 | Description: Automated unit testing of Shiny applications through a headless 'Chromium' browser. 14 | License: MIT + file LICENSE 15 | Encoding: UTF-8 16 | Language: en-US 17 | Roxygen: list(markdown = TRUE) 18 | RoxygenNote: 7.3.2 19 | URL: https://rstudio.github.io/shinytest2/, https://github.com/rstudio/shinytest2 20 | BugReports: https://github.com/rstudio/shinytest2/issues 21 | VignetteBuilder: knitr 22 | Depends: 23 | testthat (>= 3.1.2) 24 | Imports: 25 | R6 (>= 2.4.0), 26 | callr, 27 | checkmate (>= 2.0.0), 28 | chromote (>= 0.5.0), 29 | cli, 30 | fs, 31 | globals (>= 0.14.0), 32 | httr, 33 | jsonlite, 34 | pingr, 35 | rlang (>= 1.0.0), 36 | rmarkdown, 37 | shiny, 38 | withr, 39 | lifecycle 40 | Suggests: 41 | deSolve, 42 | diffobj, 43 | ggplot2, 44 | knitr, 45 | plotly, 46 | png, 47 | rstudioapi, 48 | shinyWidgets, 49 | shinytest (>= 1.5.1), 50 | shinyvalidate (>= 0.1.2), 51 | showimage, 52 | usethis, 53 | vdiffr (>= 1.0.0), 54 | spelling 55 | Config/Needs/check: 56 | rstudio/shiny, 57 | bslib 58 | Config/Needs/website: 59 | pkgdown, 60 | tidyverse/tidytemplate 61 | Config/testthat/edition: 3 62 | Collate: 63 | 'R6-helper.R' 64 | 'app-driver-chromote.R' 65 | 'app-driver-dir.R' 66 | 'app-driver-expect-download.R' 67 | 'app-driver-expect-js.R' 68 | 'app-driver-expect-screenshot.R' 69 | 'app-driver-expect-unique-names.R' 70 | 'app-driver-expect-values.R' 71 | 'app-driver-get-log.R' 72 | 'app-driver-initialize.R' 73 | 'app-driver-log-message.R' 74 | 'app-driver-message.R' 75 | 'app-driver-node.R' 76 | 'app-driver-set-inputs.R' 77 | 'app-driver-start.R' 78 | 'app-driver-stop.R' 79 | 'app-driver-timeout.R' 80 | 'app-driver-upload-file.R' 81 | 'app-driver-variant.R' 82 | 'app-driver-wait.R' 83 | 'app-driver-window.R' 84 | 'app-driver.R' 85 | 'chromote-methods.R' 86 | 'compare-screenshot-threshold.R' 87 | 'cpp11.R' 88 | 'expect-snapshot.R' 89 | 'expr-recurse.R' 90 | 'httr.R' 91 | 'migrate.R' 92 | 'missing-value.R' 93 | 'utils.R' 94 | 'platform.R' 95 | 'record-test-unique-name.R' 96 | 'record-test.R' 97 | 'rstudio.R' 98 | 'save-app.R' 99 | 'shiny-browser.R' 100 | 'shinytest2-logs.R' 101 | 'shinytest2-package.R' 102 | 'test-app.R' 103 | 'use.R' 104 | LinkingTo: 105 | cpp11 -------------------------------------------------------------------------------- /src/tests/invalid_config/bad_git_url.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "project_name" 3 | # Can specify which version of R is required, could be used later in rv as R version manager? 4 | r_version = "4.4.1" 5 | description = "" 6 | authors = [{name = "Bob", email="hello@acme.org", maintainer = true}] 7 | license = "MIT" 8 | keywords = [] 9 | 10 | # Are suggested deps also enforcing repository? Only used if you're making a library 11 | suggests = [] 12 | 13 | # Order matters 14 | repositories = [ 15 | { alias = "cran", url = "https://cran.r-project.org"}, 16 | { alias = "mpn", url = "https://mpn.metworx.com/snapshots/stable/2020-09-20"}, 17 | ] 18 | 19 | dependencies = [ 20 | "dplyr", 21 | { name = "some-package", git = "https", tag = "v0.1.1", install_suggestions = true }, 22 | ] 23 | 24 | -------------------------------------------------------------------------------- /src/tests/invalid_config/bad_repo_url.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "project_name" 3 | r_version = "4.4.1" 4 | 5 | repositories = [ 6 | { alias = "cran", url = "@CRAN@"}, 7 | ] 8 | 9 | dependencies = [ 10 | ] 11 | 12 | -------------------------------------------------------------------------------- /src/tests/invalid_config/bad_repo_url2.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "init-test" 3 | r_version = "4.4" 4 | repositories = [ { alias = "bad-url", url = "htp:/my-bad-url.c" } ] 5 | dependencies = [ "R6" ] 6 | -------------------------------------------------------------------------------- /src/tests/package_files/a2-ai-universe.PACKAGE: -------------------------------------------------------------------------------- 1 | Package: blake3r 2 | Version: 0.0.1 3 | License: MIT + file LICENSE 4 | NeedsCompilation: yes 5 | Filesize: 76790 6 | SHA256: 67321cc83ede2eec61675a2ed6f9ce775b3ad9ad74cfeb30c05b23fff1246f2b 7 | Suggests: testthat (>= 3.0.0), withr 8 | MD5sum: 457f75b7d91c0078c8152075112ed78e 9 | 10 | Package: dvs 11 | Version: 0.0.2 12 | License: MIT + file LICENSE 13 | NeedsCompilation: yes 14 | Filesize: 125099 15 | SHA256: 3b11d11a7325096240896d937a0b32294b792f9e2d4aa5fcc453e9f0e26fdf84 16 | Imports: rlang 17 | Suggests: testthat (>= 3.0.0), fs, jsonlite, withr, stringr, yaml, processx 18 | MD5sum: 0a21818065b768998be9ce4cd12dcec8 19 | 20 | Package: ghqc 21 | Version: 0.3.3 22 | License: GPL (>= 3) + file LICENSE 23 | NeedsCompilation: no 24 | Filesize: 528606 25 | SHA256: 10a053cf7e163f4b9a497825360824d59e2835de736546e364a0adc6ac1705d8 26 | Depends: R (>= 2.10) 27 | Imports: httpuv, rstudioapi, withr, glue, rlang, cli, fs, yaml 28 | Suggests: gert, pak (>= 0.8.0) 29 | MD5sum: 2f5f8594e04aa59e803f8558cc13b378 30 | 31 | Package: ghqc.app 32 | Version: 0.4.2 33 | License: GPL (>= 3) + file LICENSE 34 | NeedsCompilation: no 35 | Filesize: 219789 36 | SHA256: 573969afb132477ce095d6f6457b00e63b0556629cd2ea7618b9ff1473619fda 37 | Imports: fs (>= 1.6.3), gert (>= 2.0.1), gh (>= 1.4.0), glue (>= 1.6.2), httr2 (>= 1.0.0), miniUI (>= 0.1.1.1), kableExtra (>= 1.3.4), purrr (>= 1.0.2), rlang (>= 1.1.3), shiny (>= 1.8.0), stringr (>= 1.5.1), waiter (>= 0.2.5), withr (>= 3.0.0), yaml (>= 2.3.7), shinyjs (>= 2.1.0), dplyr (>= 1.1.4), diffobj (>= 0.3.5), processx (>= 3.8.3), rmarkdown (>= 2.24), rvest (>= 1.0.3), digest (>= 0.6.34), jsTreeR (>= 2.5.0), log4r (>= 0.4.3), rprojroot (>= 2.0.3), gitcreds (>= 0.1.2), shinyvalidate, xml2 38 | Suggests: testthat (>= 3.0.0), httptest, knitr, shinytest2 39 | MD5sum: 87f39863cce17374bf003e67f1fa5bbb 40 | 41 | Package: osinfo 42 | Version: 0.0.1 43 | License: MIT + file LICENSE 44 | NeedsCompilation: yes 45 | Filesize: 75188 46 | SHA256: a484d15fa8d399f285c6a1028acc955097ece83f57711c15b6b0d2abf969675f 47 | Suggests: testthat (>= 3.0.0) 48 | MD5sum: 9cdcbce44bc9a706f526dd2d21c32745 49 | 50 | Package: reportifyr 51 | Version: 0.2.5 52 | License: GPL (>= 3) 53 | NeedsCompilation: no 54 | Filesize: 2158407 55 | SHA256: 158dc8af3afac28980bb0b3b215eb3a9ae8ba1f40cd848828b0b1c9a74718a9d 56 | Depends: R (>= 2.10) 57 | Imports: assertthat, cli, digest, dplyr, flextable, gert, ggplot2, here, jsonlite, log4r, officer, processx, purrr, rlang, rstudioapi, tictoc, yaml 58 | Suggests: knitr, rmarkdown, testthat (>= 3.0.0) 59 | MD5sum: 83b8ba80aa6e9b08d6bdee34cd025c66 60 | 61 | Package: scicalc 62 | Version: 0.1.1 63 | License: MIT + file LICENSE 64 | NeedsCompilation: no 65 | Filesize: 249727 66 | SHA256: 1f9fd1eb2db72b8e8dc596e036804c47f7df4a5d85f7e0f386b48d70a41fff99 67 | Imports: arrow, checkmate, digest, dplyr, fs, haven, magrittr, readr, readxl, rlang, stats, stringr 68 | Suggests: knitr, rmarkdown, testthat (>= 3.0.0), ggplot2, here, purrr, pzfx, tools, tidyr 69 | MD5sum: efb885d0359014a526522422a88f2523 70 | 71 | Package: slurmtools 72 | Version: 0.1.2 73 | License: GPL (>= 3) 74 | NeedsCompilation: no 75 | Filesize: 844260 76 | SHA256: 6400ec1d6c1a2a91ec6b28e203d8e16de038797145c68a68c8672e5fc4432894 77 | Imports: brio, dplyr, fs, glue, jsonlite, processx, purrr, rlang, tibble, utils, whisker, withr, here, cli, stringi, hms, lifecycle 78 | Suggests: knitr, rmarkdown, testthat (>= 3.0.0), bbr 79 | MD5sum: 907b2a050c09f5627b6a8b94250d162f 80 | -------------------------------------------------------------------------------- /src/tests/package_files/test_repo1.PACKAGE: -------------------------------------------------------------------------------- 1 | Package: dummy 2 | Version: 0.0.0.9001 3 | Imports: R6 4 | License: MIT + file LICENSE 5 | MD5sum: b7f25d09ecab0c56f94f1238ddd604e6 6 | 7 | Package: rv.git.pkgA 8 | Version: 0.0.5 9 | License: MIT + file LICENSE 10 | MD5sum: f3dee39a353a0a872ec673557a856258 11 | 12 | Package: rv.git.pkgD 13 | Version: 0.0.1 14 | Depends: rv.git.pkgA (>= 0.0.5) 15 | License: MIT + file LICENSE 16 | MD5sum: ad2ae403ef70f3d622e4f7a6d51a3f2b 17 | 18 | Package: rv.git.pkgE 19 | Version: 0.0.0.1 20 | Depends: rv.git.pkgA (< 0.0.5) 21 | MD5sum: 5affcc1e4d9097f3ff62366c72bef1c1 22 | 23 | Package: higher-base-pkg 24 | Version: 0.0.1 25 | Depends: survival (>= 6.0.0) 26 | 27 | Package: survival 28 | Version: 6.0.0 29 | -------------------------------------------------------------------------------- /src/tests/package_files/test_repo2.PACKAGE: -------------------------------------------------------------------------------- 1 | Package: rv.git.pkgA 2 | Version: 0.0.4 3 | License: MIT + file LICENSE 4 | MD5sum: ad249f40ffe49e2310bd4f0c38328603 5 | 6 | Package: requires.old.gsm 7 | Version: 0.0.1 8 | Depends: gsm (< 2.0.0) 9 | License: MIT + file LICENSE 10 | MD5sum: ad2ae403ef70f3d622e4f7a6d51a3f2b -------------------------------------------------------------------------------- /src/tests/r_universe/arrow.api: -------------------------------------------------------------------------------- 1 | { 2 | "Package": "arrow", 3 | "Title": "Integration to 'Apache' 'Arrow'", 4 | "Version": "20.0.0", 5 | "Authors@R": "c(\nperson(\"Neal\", \"Richardson\", email = \"neal.p.richardson@gmail.com\", role = c(\"aut\")),\nperson(\"Ian\", \"Cook\", email = \"ianmcook@gmail.com\", role = c(\"aut\")),\nperson(\"Nic\", \"Crane\", email = \"thisisnic@gmail.com\", role = c(\"aut\")),\nperson(\"Dewey\", \"Dunnington\", role = c(\"aut\"), email = \"dewey@fishandwhistle.net\", comment = c(ORCID = \"0000-0002-9415-4582\")),\nperson(\"Romain\", \"Fran\\u00e7ois\", role = c(\"aut\"), comment = c(ORCID = \"0000-0002-2444-4226\")),\nperson(\"Jonathan\", \"Keane\", email = \"jkeane@gmail.com\", role = c(\"aut\", \"cre\")),\nperson(\"Drago\\u0219\", \"Moldovan-Gr\\u00fcnfeld\", email = \"dragos.mold@gmail.com\", role = c(\"aut\")),\nperson(\"Jeroen\", \"Ooms\", email = \"jeroen@berkeley.edu\", role = c(\"aut\")),\nperson(\"Jacob\", \"Wujciak-Jens\", email = \"jacob@wujciak.de\", role = c(\"aut\")),\nperson(\"Javier\", \"Luraschi\", email = \"javier@rstudio.com\", role = c(\"ctb\")),\nperson(\"Karl\", \"Dunkle Werner\", email = \"karldw@users.noreply.github.com\", role = c(\"ctb\"), comment = c(ORCID = \"0000-0003-0523-7309\")),\nperson(\"Jeffrey\", \"Wong\", email = \"jeffreyw@netflix.com\", role = c(\"ctb\")),\nperson(\"Apache Arrow\", email = \"dev@arrow.apache.org\", role = c(\"aut\", \"cph\"))\n)", 6 | "Description": "'Apache' 'Arrow' is a\ncross-language development platform for in-memory data. It\nspecifies a standardized language-independent columnar memory\nformat for flat and hierarchical data, organized for efficient\nanalytic operations on modern hardware. This package provides\nan interface to the 'Arrow C++' library.", 7 | "License": "Apache License (>= 2.0)", 8 | "URL": "https://github.com/apache/arrow/, https://arrow.apache.org/docs/r/", 9 | "BugReports": "https://github.com/apache/arrow/issues", 10 | "Encoding": "UTF-8", 11 | "Language": "en-US", 12 | "SystemRequirements": "C++17; for AWS S3 support on Linux, libcurl and\nopenssl (optional); cmake >= 3.16 (build-time only, and only\nfor full source build)", 13 | "Biarch": "true", 14 | "Roxygen": "list(markdown = TRUE, r6 = FALSE, load = \"source\")", 15 | "RoxygenNote": "7.3.1", 16 | "Config/testthat/edition": "3", 17 | "Config/build/bootstrap": "TRUE", 18 | "Collate": "'arrowExports.R' 'enums.R' 'arrow-object.R' 'type.R'\n'array-data.R' 'arrow-datum.R' 'array.R' 'arrow-info.R'\n'arrow-package.R' 'arrow-tabular.R' 'buffer.R'\n'chunked-array.R' 'io.R' 'compression.R' 'scalar.R' 'compute.R'\n'config.R' 'csv.R' 'dataset.R' 'dataset-factory.R'\n'dataset-format.R' 'dataset-partition.R' 'dataset-scan.R'\n'dataset-write.R' 'dictionary.R' 'dplyr-across.R'\n'dplyr-arrange.R' 'dplyr-by.R' 'dplyr-collect.R'\n'dplyr-count.R' 'dplyr-datetime-helpers.R' 'dplyr-distinct.R'\n'dplyr-eval.R' 'dplyr-filter.R' 'dplyr-funcs-agg.R'\n'dplyr-funcs-augmented.R' 'dplyr-funcs-conditional.R'\n'dplyr-funcs-datetime.R' 'dplyr-funcs-doc.R'\n'dplyr-funcs-math.R' 'dplyr-funcs-simple.R'\n'dplyr-funcs-string.R' 'dplyr-funcs-type.R' 'expression.R'\n'dplyr-funcs.R' 'dplyr-glimpse.R' 'dplyr-group-by.R'\n'dplyr-join.R' 'dplyr-mutate.R' 'dplyr-select.R'\n'dplyr-slice.R' 'dplyr-summarize.R' 'dplyr-union.R'\n'record-batch.R' 'table.R' 'dplyr.R' 'duckdb.R' 'extension.R'\n'feather.R' 'field.R' 'filesystem.R' 'flight.R'\n'install-arrow.R' 'ipc-stream.R' 'json.R' 'memory-pool.R'\n'message.R' 'metadata.R' 'parquet.R' 'python.R'\n'query-engine.R' 'record-batch-reader.R'\n'record-batch-writer.R' 'reexports-bit64.R'\n'reexports-tidyselect.R' 'schema.R' 'udf.R' 'util.R'", 19 | "Config/pak/sysreqs": "cmake libssl-dev", 20 | "Repository": "https://apache.r-universe.dev", 21 | "RemoteUrl": "https://github.com/apache/arrow", 22 | "RemoteRef": "apache-arrow-20.0.0", 23 | "RemoteSha": "3ad0370a04ccdae638755b94c3c31c8760a11193", 24 | "RemoteSubdir": "r", 25 | "NeedsCompilation": "yes", 26 | "Packaged": { 27 | "Date": "2025-04-30 10:42:38 UTC", 28 | "User": "root" 29 | }, 30 | "Author": "Neal Richardson [aut],\nIan Cook [aut],\nNic Crane [aut],\nDewey Dunnington [aut] (ORCID: ),\nRomain François [aut] (ORCID: ),\nJonathan Keane [aut, cre],\nDragoș Moldovan-Grünfeld [aut],\nJeroen Ooms [aut],\nJacob Wujciak-Jens [aut],\nJavier Luraschi [ctb],\nKarl Dunkle Werner [ctb] (ORCID:\n),\nJeffrey Wong [ctb],\nApache Arrow [aut, cph]", 31 | "Maintainer": "Jonathan Keane ", 32 | "MD5sum": "8b6bd70fdd32e8ded312c9e109cda269", 33 | "_user": "apache", 34 | "_type": "src", 35 | "_file": "arrow_20.0.0.tar.gz", 36 | "_fileid": "2a3b9e615e0d8e2f475c0c51ede643f2b58ee78dd1585f2ae8159b7ad06a3dc0", 37 | "_filesize": 4957541, 38 | "_sha256": "2a3b9e615e0d8e2f475c0c51ede643f2b58ee78dd1585f2ae8159b7ad06a3dc0", 39 | "_created": "2025-04-30T10:42:38.000Z", 40 | "_published": "2025-04-30T11:32:50.446Z", 41 | "_upstream": "https://github.com/apache/arrow", 42 | "_commit": { 43 | "id": "3ad0370a04ccdae638755b94c3c31c8760a11193", 44 | "author": "Jacob Wujciak-Jens ", 45 | "committer": "Jacob Wujciak-Jens ", 46 | "message": "MINOR: [Release] Update versions for 20.0.0\n", 47 | "time": 1745344695 48 | }, 49 | "_maintainer": { 50 | "name": "Jonathan Keane", 51 | "email": "jkeane@gmail.com", 52 | "login": "jonkeane", 53 | "mastodon": "@jonkeane@mastodon.social", 54 | "uuid": 700357 55 | } 56 | } -------------------------------------------------------------------------------- /src/tests/r_universe/osinfo.api: -------------------------------------------------------------------------------- 1 | { 2 | "Package": "osinfo", 3 | "Title": "Get the os info in a cross platform way", 4 | "Version": "0.0.1", 5 | "Authors@R": "person(\"Devin\", \"Pastoor\", , \"devin.pastoor@gmail.com\", role = c(\"aut\", \"cre\"))", 6 | "Description": "Get the os info in a cross platform way.", 7 | "License": "MIT + file LICENSE", 8 | "Encoding": "UTF-8", 9 | "Roxygen": "list(markdown = TRUE)", 10 | "RoxygenNote": "7.3.2", 11 | "Config/rextendr/version": "0.3.1.9001", 12 | "SystemRequirements": "Cargo (Rust's package manager), rustc", 13 | "Config/testthat/edition": "3", 14 | "Repository": "https://a2-ai.r-universe.dev", 15 | "RemoteUrl": "https://github.com/a2-ai/osinfo", 16 | "RemoteRef": "HEAD", 17 | "RemoteSha": "f815095b7b04cbf57da0e0c0a55ef5e03c16f477", 18 | "NeedsCompilation": "yes", 19 | "Packaged": { 20 | "Date": "2025-04-15 02:52:43 UTC", 21 | "User": "root" 22 | }, 23 | "Author": "Devin Pastoor [aut, cre]", 24 | "Maintainer": "Devin Pastoor ", 25 | "MD5sum": "890f6690dbcdfe35d361c3a44281effd", 26 | "_user": "a2-ai", 27 | "_type": "src", 28 | "_file": "osinfo_0.0.1.tar.gz", 29 | "_fileid": "1117a0127fa57a199e65d38b678b4c51ab19600ccbe5c6dfa8549d021237199a", 30 | "_filesize": 74951, 31 | "_sha256": "1117a0127fa57a199e65d38b678b4c51ab19600ccbe5c6dfa8549d021237199a", 32 | "_created": "2025-04-15T02:52:43.000Z", 33 | "_published": "2025-04-15T02:58:03.350Z", 34 | "_upstream": "https://github.com/a2-ai/osinfo", 35 | "_commit": { 36 | "id": "f815095b7b04cbf57da0e0c0a55ef5e03c16f477", 37 | "author": "Devin Pastoor ", 38 | "committer": "GitHub ", 39 | "message": "Update DESCRIPTION", 40 | "time": 1734308570 41 | }, 42 | "_maintainer": { 43 | "name": "Devin Pastoor", 44 | "email": "devin.pastoor@gmail.com", 45 | "login": "dpastoor", 46 | "uuid": 3196313 47 | } 48 | } -------------------------------------------------------------------------------- /src/tests/renv/renv.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.4.1", 4 | "Repositories": [ 5 | { 6 | "Name": "gh-pkg-mirror", 7 | "URL": "https://gh-pkg-mirror" 8 | }, 9 | { 10 | "Name": "cran-binary", 11 | "URL": "https://cran-binary" 12 | } 13 | ] 14 | }, 15 | "Packages": { 16 | "Matrix": { 17 | "Package": "Matrix", 18 | "Version": "1.7-0", 19 | "Source": "Repository", 20 | "Repository": "CRAN", 21 | "Requirements": [ 22 | "R", 23 | "grDevices", 24 | "graphics", 25 | "grid", 26 | "lattice", 27 | "methods", 28 | "stats", 29 | "utils" 30 | ] 31 | }, 32 | "R6": { 33 | "Package": "R6", 34 | "Version": "2.5.0", 35 | "Source": "Repository", 36 | "Repository": "cran-binary", 37 | "Hash": "470851b6d5d0ac559e9d01bb352b4021" 38 | }, 39 | "ghqc": { 40 | "Package": "ghqc", 41 | "Version": "0.3.2", 42 | "Source": "GitHub", 43 | "RemoteType": "github", 44 | "RemoteHost": "api.github.com", 45 | "RemoteRepo": "ghqc", 46 | "RemoteUsername": "a2-ai", 47 | "RemoteSha": "55c23eb6a444542dab742d3d37c7b65af7b12e38", 48 | "Requirements": [ 49 | "R", 50 | "cli", 51 | "fs", 52 | "glue", 53 | "httpuv", 54 | "rlang", 55 | "rstudioapi", 56 | "withr", 57 | "yaml" 58 | ], 59 | "Hash": "dcba3cb6539ee3cfce6218049c5016cc" 60 | }, 61 | "rv.git.pkgA": { 62 | "Package": "rv.git.pkgA", 63 | "Version": "0.0.0.9000", 64 | "Source": "Local", 65 | "RemoteType": "local", 66 | "RemoteUrl": "src/tests/renv/rv.git.pkgA_0.0.0.9000.tar.gz", 67 | "Hash": "39e317a9ec5437bd5ce021ad56da04b6" 68 | }, 69 | "slurmtools": { 70 | "Package": "slurmtools", 71 | "Version": "0.1.2", 72 | "Source": "Repository", 73 | "Repository": "gh-pkg-mirror", 74 | "Requirements": [ 75 | "brio", 76 | "cli", 77 | "dplyr", 78 | "fs", 79 | "glue" 80 | ], 81 | "Hash": "f1e4f6bf3dcef58d3fe9865060f2a8b7" 82 | }, 83 | "simpar": { 84 | "Package": "simpar", 85 | "Version": "0.1.1", 86 | "Source": "Repository", 87 | "Repository": "PRISM", 88 | "Requirements": [ 89 | "arrow", 90 | "checkmate", 91 | "digest" 92 | ], 93 | "Hash": "b574c1bce8b11eec3b135f7c585a04c7" 94 | }, 95 | "unknown_pkg": { 96 | "Package": "unknown_pkg", 97 | "Version": "0.1.1", 98 | "Source": "unknown", 99 | "Hash": "b574c1bce8b11eec3b135f7c585a04c7" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/tests/resolution/case_sensitive.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | "r6", 7 | ] 8 | --- 9 | Package: R6 10 | Version: 2.5.1 11 | Depends: R (>= 3.0) 12 | Suggests: testthat, pryr 13 | NeedsCompilation: no 14 | License: MIT + file LICENSE 15 | --- -------------------------------------------------------------------------------- /src/tests/resolution/conflict_dep_requirement.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [ 5 | { alias = "repo1", url = "http://repo1" }, 6 | { alias = "repo2", url = "http://repo2" } 7 | ] 8 | dependencies = [ 9 | # This package wants A >= 0.0.5 10 | "rv.git.pkgD", 11 | # This repo only contains 0.0.4 12 | { name = "rv.git.pkgA", repository = "repo2" }, 13 | ] 14 | --- 15 | repos = [ 16 | {name = "repo1", source = "test_repo1", force_source = false}, 17 | {name = "repo2", source = "test_repo2", force_source = false}, 18 | ] 19 | --- -------------------------------------------------------------------------------- /src/tests/resolution/conflict_dep_requirement2.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [ 5 | { alias = "repo1", url = "http://repo1" }, 6 | { alias = "repo2", url = "http://repo2" } 7 | ] 8 | dependencies = [ 9 | # This package wants A >= 0.0.5 10 | "rv.git.pkgD", 11 | # This package wants A < 0.0.5 12 | "rv.git.pkgE", 13 | ] 14 | --- 15 | repos = [ 16 | {name = "repo1", source = "test_repo1", force_source = false}, 17 | {name = "repo2", source = "test_repo2", force_source = false}, 18 | ] 19 | --- -------------------------------------------------------------------------------- /src/tests/resolution/conflict_dep_requirement3.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "remote" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"}, 6 | { alias = "repo2", url = "http://repo2" } 7 | ] 8 | dependencies = [ 9 | # This one has a gsm dep of >= 2.2.2 with a remote set 10 | { name = "gsm.app", git = "https://github.com/Gilead-BioStats/gsm.app", tag ="v2.3.0"}, 11 | # This requires gsm < 2.0.0 so it will conflict 12 | { name = "requires.old.gsm" , repository = "repo2" }, 13 | ] 14 | 15 | --- 16 | repos = [ 17 | {name = "repo2", source = "test_repo2", force_source = false}, 18 | {name = "custom", binary = "cran-binary", force_source = false} 19 | ] 20 | --- -------------------------------------------------------------------------------- /src/tests/resolution/dep_force_source_lockfile.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | # If we have it already as binary in the lockfile, we'll need to fetch it from the repo 7 | { name = "R6", force_source = true }, 8 | ] 9 | --- 10 | repos = [{name = "cran", binary = "cran-binary", source = "posit-src", force_source = false}] 11 | --- 12 | version = 1 13 | r_version = "4.4" 14 | 15 | [[packages]] 16 | name = "r6" 17 | version = "2.5.1" 18 | source = { repository = "http://cran" } 19 | force_source = false 20 | dependencies = [] -------------------------------------------------------------------------------- /src/tests/resolution/dep_force_source_no_lockfile.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | { name = "R6", force_source = true }, 7 | ] 8 | --- 9 | repos = [{name = "cran", binary = "cran-binary", source = "posit-src", force_source = false}] 10 | --- -------------------------------------------------------------------------------- /src/tests/resolution/dep_force_source_override_repo.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | { name = "dplyr", force_source = false }, 7 | ] 8 | --- 9 | repos = [{name = "cran", binary = "cran-binary", source = "posit-src", force_source = true}] 10 | --- -------------------------------------------------------------------------------- /src/tests/resolution/dependencies_only.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | {name = "dplyr", dependencies_only = true}, 7 | ] 8 | --- 9 | repos = [{name = "cran", binary = "cran-binary", force_source = false}] 10 | --- -------------------------------------------------------------------------------- /src/tests/resolution/different_repo_from_lockfile.txt: -------------------------------------------------------------------------------- 1 | # even with a higher version in the package db it should prefer the lockfile 2 | [project] 3 | name = "test" 4 | r_version = "4.4" 5 | repositories = [] 6 | # This will fetch it from the 'test' repository even though it's in the lockfile with a different source 7 | # since the cran source is not in the config 8 | dependencies = [ 9 | "R6", 10 | ] 11 | --- 12 | repos = [{name = "test", binary = "cran-binary", source = "posit-src", force_source = false}] 13 | --- 14 | version = 1 15 | r_version = "4.4" 16 | 17 | [[packages]] 18 | name = "R6" 19 | version = "2.5.1" 20 | source = { repository = "http://cran" } 21 | force_source = false 22 | dependencies = [] 23 | -------------------------------------------------------------------------------- /src/tests/resolution/dplyr_no_lockfile.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | "dplyr", 7 | ] 8 | --- 9 | repos = [{name = "cran", binary = "cran-binary", force_source = false}] 10 | --- -------------------------------------------------------------------------------- /src/tests/resolution/dupe_imports_linktsto.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | "httpuv", 7 | ] 8 | --- 9 | repos = [{name = "cran", binary = "cran-binary", force_source = false}] 10 | --- -------------------------------------------------------------------------------- /src/tests/resolution/git_pkg_with_remote_dep.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "remote" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | # This one has a git remote for a dep 9 | { name = "gsm", git = "https://github.com/Gilead-BioStats/gsm", tag ="v2.2.2", install_suggestions = true}, 10 | ] 11 | --- 12 | repos = [{name = "posit", source = "posit-src", force_source = false}] 13 | --- -------------------------------------------------------------------------------- /src/tests/resolution/git_pkg_with_remote_dep_overridden.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "remote" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | # This one has a gsm dep of >= 2.2.2 with a remote set 9 | { name = "gsm.app", git = "https://github.com/Gilead-BioStats/gsm.app", tag ="v2.3.0"}, 10 | ] 11 | # we want the built binary from the repo instead of the remote 12 | prefer_repositories_for = ["gsm"] 13 | --- 14 | repos = [{name = "custom", binary = "cran-binary", force_source = false}] 15 | --- -------------------------------------------------------------------------------- /src/tests/resolution/git_pkg_wrong_name.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "remote" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | { name = "wrong", git = "https://github.com/Gilead-BioStats/gsm", tag ="v2.2.2", install_suggestions = true}, 9 | ] 10 | --- 11 | repos = [{name = "posit", source = "posit-src", force_source = false}] 12 | --- -------------------------------------------------------------------------------- /src/tests/resolution/higher_r_version.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.5" 4 | repositories = [] 5 | dependencies = [ 6 | # 2.1.8 is later in the file but it should return 2.1.7 because it has a higher R requirement that is matched 7 | "cluster", 8 | ] 9 | --- 10 | repos = [{name = "posit", binary = "posit-src", force_source = false}] 11 | --- -------------------------------------------------------------------------------- /src/tests/resolution/local_dep.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | { name = "dummy", path = "dummy-pkg" } 7 | ] 8 | --- 9 | repos = [{name = "cran", binary = "cran-binary", force_source = false}] 10 | --- -------------------------------------------------------------------------------- /src/tests/resolution/local_dep_not_found.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | { name = "dummy", path = "unknown" } 7 | ] 8 | --- 9 | --- -------------------------------------------------------------------------------- /src/tests/resolution/local_dep_wrong_name.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | { name = "wrong", path = "dummy-pkg" } 7 | ] 8 | --- 9 | --- -------------------------------------------------------------------------------- /src/tests/resolution/multi-repo-combination-no-suggests.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | "slurmtools", 7 | ] 8 | --- 9 | repos = [ 10 | { name = "a2-ai-universe", source = "a2-ai-universe", force_source = false }, 11 | { name = "P3M", binary = "posit-src", force_source = false } 12 | #{ name = "gh-pkg-mirror", binary = "gh-pkg-mirror", force_source = false}, 13 | ] 14 | --- -------------------------------------------------------------------------------- /src/tests/resolution/multi-repo-combination-suggests-complete.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | # this will now fail since bbr as a suggested dep is not here 7 | {name = "slurmtools", install_suggestions = true } 8 | ] 9 | --- 10 | repos = [ 11 | { name = "a2-ai-universe", source = "a2-ai-universe", force_source = false }, 12 | { name = "P3M", binary = "posit-src", force_source = false }, 13 | { name = "gh-pkg-mirror", source = "gh-pkg-mirror", force_source = false } 14 | ] 15 | --- -------------------------------------------------------------------------------- /src/tests/resolution/multi-repo-combination-suggests-failure.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | # this will now fail since bbr as a suggested dep is not here 7 | {name = "slurmtools", install_suggestions = true } 8 | ] 9 | --- 10 | repos = [ 11 | { name = "a2-ai-universe", source = "a2-ai-universe", force_source = false }, 12 | { name = "P3M", binary = "posit-src", force_source = false } 13 | ] 14 | --- -------------------------------------------------------------------------------- /src/tests/resolution/path.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "path" 3 | r_version = "4.5" 4 | repositories = [ 5 | { alias = "posit", url = "http://posit" }, 6 | ] 7 | dependencies = [ 8 | # It should resolve to the version from the repository, the one version with a path to 4.5.0 9 | {name = "MASS", repository = "posit"} 10 | ] 11 | --- 12 | repos = [{name = "posit", source = "posit-src", force_source = false}] 13 | --- -------------------------------------------------------------------------------- /src/tests/resolution/pkg_listed_in_config_missing_from_remote.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "remote" 3 | r_version = "4.4" 4 | repositories = [ 5 | {alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"} 6 | ] 7 | dependencies = [ 8 | # this package is missing the remote for gsm so it will fail by itself 9 | { name = "missing.remote", git = "https://github.com/dummy/missing.remote", branch = "main"}, 10 | # but if we list it directly, it should use it if the version requirement match 11 | { name = "gsm", git = "https://github.com/Gilead-BioStats/gsm", tag ="v2.2.2", install_suggestions = true}, 12 | ] 13 | --- 14 | repos = [{name = "posit", source = "posit-src", force_source = false}] 15 | --- -------------------------------------------------------------------------------- /src/tests/resolution/r_universe_lockfile.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "r-universe" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | "osinfo" 7 | ] 8 | --- 9 | repos = [ 10 | {name = "r-universe.dev", source = "a2-ai-universe", force_source = false}, 11 | ] 12 | --- 13 | version = 1 14 | r_version = "4.4" 15 | 16 | [[packages]] 17 | name = "osinfo" 18 | version = "0.0.1" 19 | source = { repository = "http://r-universe.dev", git = "https://github.com/a2-ai/osinfo", sha = "f815095b7b04cbf57da0e0c0a55ef5e03c16f477" } 20 | force_source = false 21 | dependencies = [] -------------------------------------------------------------------------------- /src/tests/resolution/r_universe_no_lockfile.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "r-universe" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | "osinfo" 7 | ] 8 | --- 9 | repos = [ 10 | {name = "r-universe.dev", source = "a2-ai-universe", force_source = false}, 11 | ] 12 | --- -------------------------------------------------------------------------------- /src/tests/resolution/recommended.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "example" 3 | r_version = "4.4" 4 | 5 | repositories = [] 6 | 7 | dependencies = [ 8 | "mrgsolve", 9 | "rmarkdown", 10 | "tidyverse", 11 | "here", 12 | "flextable", 13 | "sessioninfo", 14 | "devtools", 15 | "arrow", 16 | "janitor" 17 | ] 18 | --- 19 | repos = [{name = "posit", source = "posit-src", force_source = false}] 20 | --- -------------------------------------------------------------------------------- /src/tests/resolution/recommended_from_repo_w_lockfile.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [ 5 | { alias = "posit", url = "http://posit" }, 6 | ] 7 | dependencies = [ 8 | # It should resolve to the version from the repository 9 | {name = "MASS", repository = "posit"} 10 | ] 11 | --- 12 | repos = [{name = "posit", source = "posit-src", force_source = false}] 13 | --- 14 | version = 2 15 | r_version = "4.4" 16 | 17 | [[packages]] 18 | name = "MASS" 19 | version = "7.3-64" 20 | source = { builtin = true } 21 | force_source = false 22 | dependencies = [] 23 | -------------------------------------------------------------------------------- /src/tests/resolution/recommended_pkg_specified.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [ 5 | { alias = "repo1", url = "http://repo1" }, 6 | ] 7 | dependencies = [ 8 | # This requires survival >= 6.0.0, there is a built in survival but not at that version 9 | "higher-base-pkg", 10 | ] 11 | --- 12 | repos = [ 13 | {name = "repo1", source = "test_repo1", force_source = false}, 14 | ] 15 | --- -------------------------------------------------------------------------------- /src/tests/resolution/recommended_pkg_specified_from_lockfile.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [ 5 | { alias = "repo1", url = "http://repo1" }, 6 | ] 7 | dependencies = [ 8 | # This requires survival >= 6.0.0, there is a survival in the lockfile but that doesn't satisfy the req 9 | "higher-base-pkg", 10 | ] 11 | --- 12 | repos = [ 13 | {name = "repo1", source = "test_repo1", force_source = false}, 14 | ] 15 | --- 16 | version = 1 17 | r_version = "4.4" 18 | 19 | [[packages]] 20 | name = "survival" 21 | version = "2.5.1" 22 | source = { builtin = true } 23 | force_source = false 24 | dependencies = [] 25 | -------------------------------------------------------------------------------- /src/tests/resolution/repo_priority_order.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | # It should get the package from the first repo 7 | "texPreview", 8 | ] 9 | --- 10 | repos = [{ name = "gh-pkg-mirror", binary = "gh-pkg-mirror", force_source = false}, {name = "cran", binary = "cran-binary", force_source = false}] 11 | --- -------------------------------------------------------------------------------- /src/tests/resolution/simple_lockfile.txt: -------------------------------------------------------------------------------- 1 | # even with a higher version in the package db it should prefer the lockfile 2 | [project] 3 | name = "test" 4 | r_version = "4.4" 5 | repositories = [] 6 | dependencies = [ 7 | "R6", 8 | ] 9 | --- 10 | Package: R6 11 | Version: 2.5.2 12 | Depends: R (>= 3.0) 13 | Suggests: testthat, pryr 14 | NeedsCompilation: no 15 | License: MIT + file LICENSE 16 | --- 17 | version = 1 18 | r_version = "4.4" 19 | 20 | [[packages]] 21 | name = "R6" 22 | version = "2.5.1" 23 | source = { repository = "http://cran" } 24 | force_source = false 25 | dependencies = [] 26 | -------------------------------------------------------------------------------- /src/tests/resolution/simple_no_lockfile.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | "R6", 7 | ] 8 | --- 9 | Package: R6 10 | Version: 2.5.1 11 | Depends: R (>= 3.0) 12 | Suggests: testthat, pryr 13 | NeedsCompilation: no 14 | License: MIT + file LICENSE 15 | --- -------------------------------------------------------------------------------- /src/tests/resolution/suggestions.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | # The lockfile is generated without install_suggestions = true 6 | # so a new call to the resolution should invalidate the lockfile 7 | dependencies = [ 8 | { name = "A3", install_suggestions = true } 9 | ] 10 | --- 11 | repos = [{name = "cran", binary = "cran-binary", force_source = false}] 12 | --- 13 | # This file is automatically @generated by rv. 14 | # It is not intended for manual editing. 15 | version = 1 16 | r_version = "4.4" 17 | 18 | [[packages]] 19 | name = "A3" 20 | version = "1.0.0" 21 | source = { repository = "http://cran" } 22 | force_source = false 23 | dependencies = [ 24 | "xtable", 25 | "pbapply", 26 | ] 27 | 28 | [[packages]] 29 | name = "pbapply" 30 | version = "1.7-2" 31 | source = { repository = "http://cran" } 32 | force_source = false 33 | dependencies = [] 34 | 35 | [[packages]] 36 | name = "xtable" 37 | version = "1.8-4" 38 | source = { repository = "http://cran" } 39 | force_source = false 40 | dependencies = [] 41 | -------------------------------------------------------------------------------- /src/tests/resolution/unmet_version_req.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [] 5 | dependencies = [ 6 | # this package expects a version greater than what exists in the db 7 | "unmet-version-req", 8 | ] 9 | --- 10 | Package: zzlite 11 | Version: 0.1.2 12 | Depends: R (>= 3.2) 13 | NeedsCompilation: no 14 | License: GPL-3 15 | 16 | Package: unmet-version-req 17 | Version: 0.0.1 18 | Depends: R (>= 2.4.0), zzlite (>= 1.0) 19 | NeedsCompilation: no 20 | --- -------------------------------------------------------------------------------- /src/tests/resolution/url-dep.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "simple" 3 | r_version = "4.4" 4 | repositories = [ 5 | ] 6 | dependencies = [ 7 | {name = "dplyr", url = "https://cran.r-project.org/src/contrib/Archive/dplyr/dplyr_1.1.3.tar.gz"} 8 | ] 9 | --- 10 | repos = [{name = "posit", binary = "posit-src", force_source = false}] 11 | --- -------------------------------------------------------------------------------- /src/tests/resolution/valid_from_multiple_repos.txt: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "test" 3 | r_version = "4.4" 4 | repositories = [ 5 | { alias = "repo1", url = "http://repo1" }, 6 | { alias = "repo2", url = "http://repo2" } 7 | ] 8 | dependencies = [ 9 | # Present in both repos but it should pick it up from repo1 10 | "rv.git.pkgA", 11 | ] 12 | --- 13 | repos = [ 14 | {name = "repo1", source = "test_repo1", force_source = false}, 15 | {name = "repo2", source = "test_repo2", force_source = false}, 16 | ] 17 | --- -------------------------------------------------------------------------------- /src/tests/valid_config/all_fields.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "project_name" 3 | # Can specify which version of R is required, could be used later in rv as R version manager? 4 | r_version = "4.4.1" 5 | description = "" 6 | authors = [{name = "Bob", email="hello@acme.org", maintainer = true}] 7 | license = "MIT" 8 | keywords = [] 9 | 10 | # Are suggested deps also enforcing repository? Only used if you're making a library 11 | suggests = [] 12 | 13 | # Order matters 14 | repositories = [ 15 | { alias = "cran", url = "https://cran.r-project.org"}, 16 | { alias = "mpn", url = "https://mpn.metworx.com/snapshots/stable/2020-09-20"}, 17 | ] 18 | 19 | dependencies = [ 20 | "dplyr", 21 | { name = "some-package", repository = "mpn", install_suggestions = true }, 22 | { name = "some-package", path = "../mpn", install_suggestions = true }, 23 | { name = "some-package", git = "https://github.com/A2-ai/scicalc", tag = "v0.1.1", install_suggestions = true }, 24 | { name = "some-package", git = "https://github.com/A2-ai/scicalc", commit = "bc50e550e432c3c620714f30dd59115801f89995", install_suggestions = true }, 25 | { name = "some-package", git = "git@github.com:username/repo.git", commit = "bc50e550e432c3c620714f30dd59115801f89995", install_suggestions = true }, 26 | ] 27 | 28 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::time::Duration; 3 | 4 | use indicatif::{ProgressBar, ProgressStyle}; 5 | 6 | use crate::consts::NUM_CPUS_ENV_VAR_NAME; 7 | 8 | pub(crate) fn get_max_workers() -> usize { 9 | std::env::var(NUM_CPUS_ENV_VAR_NAME) 10 | .ok() 11 | .and_then(|x| x.parse::().ok()) 12 | .unwrap_or_else(num_cpus::get) 13 | } 14 | 15 | pub(crate) fn create_spinner(visible: bool, message: impl Into>) -> ProgressBar { 16 | if visible { 17 | let pb = ProgressBar::new(10); 18 | pb.set_style( 19 | ProgressStyle::with_template("{spinner} {wide_msg}") 20 | .unwrap() 21 | .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "), 22 | ); 23 | pb.enable_steady_tick(Duration::from_millis(100)); 24 | pb.set_message(message); 25 | pb 26 | } else { 27 | ProgressBar::hidden() 28 | } 29 | } 30 | --------------------------------------------------------------------------------