Skip to content

Commit 10e053e

Browse files
authored
Merge pull request #19 from A2-ai/windows
Windows Support + Bug Fixes
2 parents 0dab95c + 6f8127e commit 10e053e

15 files changed

+487
-68
lines changed

.github/workflows/R-CMD-check.yaml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
33
on:
44
push:
5-
branches: main
5+
branches: [main, windows]
66
pull_request:
77
branches: main
88

@@ -20,6 +20,8 @@ jobs:
2020
fail-fast: false
2121
matrix:
2222
config:
23+
- {os: macos-latest, r: '4.5'}
24+
- {os: windows-latest, r: '4.5'}
2325
- {os: ubuntu-latest, r: '4.5'}
2426
- {os: ubuntu-latest, r: '4.4'}
2527
- {os: ubuntu-latest, r: '4.3'}
@@ -44,11 +46,27 @@ jobs:
4446
run: |
4547
perl -i -pe 's|^r_version = "4\.[0-9\.]+"$|r_version = "${{ matrix.config.r }}"|' rproject.toml
4648
cat rproject.toml
49+
if: runner.os != 'Windows'
50+
51+
- name: replace-rversion (Windows)
52+
run: |
53+
$rVersion = "${{ matrix.config.r }}"
54+
(Get-Content rproject.toml) -replace '^r_version = "4\.[0-9\.]+"$', "r_version = `"$rVersion`"" | Set-Content rproject.toml
55+
Get-Content rproject.toml
56+
shell: pwsh
57+
if: runner.os == 'Windows'
4758

4859
- name: rv-plan
4960
run: rv plan
61+
if: runner.os != 'Windows'
62+
63+
- name: rv-plan (Windows)
64+
run: rv.exe plan
65+
shell: cmd
66+
if: runner.os == 'Windows'
5067

5168
- name: rv-sysdeps-install (Linux)
69+
if: runner.os == 'Linux'
5270
run: |
5371
MISSING_DEPS=$(rv plan --json | jq -r '.installed[].sys_deps[]? | select(.status == "absent") | .name')
5472
if [ -n "$MISSING_DEPS" ]; then
@@ -59,12 +77,19 @@ jobs:
5977
else
6078
echo "All system dependencies are present"
6179
fi
80+
6281
- name: rv-sync
6382
run: rv sync
83+
if: runner.os != 'Windows'
6484

6585
- run: git config --global user.email "[email protected]"
6686
- run: git config --global user.name "r-cmd-check"
6787

88+
- name: rv-sync (Windows)
89+
run: rv.exe sync
90+
shell: cmd
91+
if: runner.os == 'Windows'
92+
6893
- uses: r-lib/actions/check-r-package@v2
6994
with:
7095
upload-snapshots: true

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: reportifyr
22
Title: Reproducible Reporting Made Simple with R
3-
Version: 0.3.1
3+
Version: 0.3.2
44
Authors@R: c(
55
person("Jacob", "Dumbleton", , "[email protected]", role = c("aut", "cre")),
66
person("Matthew", "Smith", , "[email protected]", role = "aut"),

NEWS.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
# reportifyr 0.3.2
2+
## Improvements
3+
4+
* Added Windows support for `uv` installation, ensuring the setup script now works on both Unix-like (Unix/Mac) and Windows platforms.
5+
6+
## Minor Improvements
7+
8+
* Added a `uv` version check such that the installed version is compared against requested version before installing requested version.
9+
* Removed comments from JSON outputs.
10+
11+
## Bug Fixes
12+
13+
* Fixed changes to `.python_dependency_version.json` in `.venv` not being propagated to the `.python_dependency_version.json` in `report_dir_name`.
14+
* Fixed an issue where user supplied `python.version` via `options('python.version')` was creating a new line in both `.python_dependency_version.json` files.
15+
116
# reportifyr 0.3.1
217
## Minor Improvements
318

R/initialize_python.R

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ initialize_python <- function(continue = NULL) {
1717
continue <- continue()
1818
}
1919

20+
# Handle logical TRUE/FALSE as well as character Y/n
21+
if (is.logical(continue)) {
22+
continue <- if (continue) "Y" else "n"
23+
}
24+
2025
if (tolower(continue) != "y") {
2126
if (continue == "n") {
2227
log4r::info(.le$logger, "User declined installation. No changes made.")
@@ -34,7 +39,14 @@ initialize_python <- function(continue = NULL) {
3439

3540
log4r::info(.le$logger, "Installation confirmed.")
3641

37-
cmd <- system.file("scripts/uv_setup.sh", package = "reportifyr")
42+
# Detect platform and choose appropriate script
43+
if (.Platform$OS.type == "windows") {
44+
cmd <- system.file("scripts/uv_setup.ps1", package = "reportifyr")
45+
log4r::info(.le$logger, "Windows platform detected, using PowerShell script")
46+
} else {
47+
cmd <- system.file("scripts/uv_setup.sh", package = "reportifyr")
48+
log4r::info(.le$logger, "Unix-like platform detected, using bash script")
49+
}
3850
log4r::info(
3951
.le$logger,
4052
paste0("Command for setting up virtual environment: ", cmd)
@@ -58,19 +70,37 @@ initialize_python <- function(continue = NULL) {
5870
is_new_env <- !dir.exists(venv_dir)
5971

6072
# Run the setup script for both new and existing environments
61-
result <- processx::run(
62-
command = cmd,
63-
args = args
64-
)
73+
if (.Platform$OS.type == "windows") {
74+
# Run PowerShell script on Windows
75+
result <- processx::run(
76+
command = "powershell.exe",
77+
args = c("-ExecutionPolicy", "Bypass", "-File", cmd, args)
78+
)
79+
} else {
80+
# Run bash script on Unix-like systems
81+
result <- processx::run(
82+
command = cmd,
83+
args = args
84+
)
85+
}
6586
# Get Python version AFTER environment exists
6687
pyvers <- get_py_version(getOption("venv_dir"))
6788
if (!is.null(pyvers)) {
68-
log4r::info(.le$logger, paste0("Python version detected: ", pyvers))
69-
# Add Python version to args for metadata
70-
args <- c(args, pyvers)
89+
# Find the index for "python.version" in args_name
90+
idx <- match("python.version", args_name)
91+
if (!is.na(idx) && length(args) >= idx) {
92+
args[idx] <- pyvers # replace existing value
93+
} else {
94+
args <- c(args, pyvers) # append if not already present
95+
}
7196
} else {
7297
log4r::warn(.le$logger, "Python version could not be detected")
73-
args <- c(args, "")
98+
idx <- match("python.version", args_name)
99+
if (!is.na(idx) && length(args) >= idx) {
100+
args[idx] <- ""
101+
} else {
102+
args <- c(args, "")
103+
}
74104
}
75105

76106
if (is_new_env) {
@@ -89,14 +119,18 @@ initialize_python <- function(continue = NULL) {
89119
}
90120

91121
message(result$stdout)
122+
123+
# Refresh uv_path after installation in case it was just installed
124+
uv_path <- get_uv_path(quiet = TRUE)
125+
92126
# Log appropriate message based on whether we created or used existing environment
93127
if (is_new_env) {
94128
log4r::info(
95129
.le$logger,
96130
paste("Virtual environment created at: ", venv_dir)
97131
)
98132
log4r::debug(.le$logger, ".venv created")
99-
} else if (file.exists(uv_path)) {
133+
} else if (!is.null(uv_path) && file.exists(uv_path)) {
100134
log4r::info(
101135
.le$logger,
102136
paste("Virtual environment already present at: ", venv_dir)

R/initialize_report_project.R

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ initialize_report_project <- function(
6262
message(
6363
"reportifyr has already been initialized. Syncing with config file now."
6464
)
65+
66+
# Check if .venv directory still exists, recreate if missing
67+
uv_path <- get_uv_path(quiet = TRUE)
68+
args <- get_args(uv_path)
69+
venv_dir <- file.path(args[[1]], ".venv")
70+
71+
if (!dir.exists(venv_dir)) {
72+
log4r::warn(.le$logger, ".venv directory missing, reinitializing Python environment")
73+
message("Python virtual environment missing. Reinitializing...")
74+
metadata_path <- initialize_python()
75+
}
76+
6577
sync_report_project(project_dir, report_dir_name)
6678
}
6779

@@ -262,17 +274,11 @@ create_init_file <- function(project_dir, report_dir, outputs_dir) {
262274
json_data <- jsonlite::toJSON(data, pretty = TRUE, auto_unbox = TRUE)
263275
log4r::debug(.le$logger, "Data converted to json string")
264276

265-
log4r::debug(.le$logger, "Adding warning message to json")
266-
json_with_comment <- paste(
267-
"// WARNING: This file is automatically generated on initialization. Do not edit by hand!",
268-
json_data,
269-
sep = "\n"
270-
)
271277
path_name <- sub("/", "_", fs::path_rel(report_dir, project_dir))
272278
init_file <- file.path(project_dir, paste0(".", path_name, "_init.json"))
273-
write(json_with_comment, file = init_file)
279+
write(json_data, file = init_file)
274280

275281
log4r::info(.le$logger, paste0("metadata written to file: ", init_file))
276282

277-
log4r::debug(.le$logger, "Exiting write_package_version_metadata function")
283+
log4r::debug(.le$logger, "Exiting create_init_file function")
278284
}

R/sync_report_project.R

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ sync_report_project <- function(project_dir, report_dir_name = NULL) {
1919
# read in init.json files
2020
if (!is.null(report_dir_name)) {
2121
path_name <- sub("/", "_", report_dir_name)
22+
report_dir <- file.path(project_dir, report_dir_name)
2223
} else {
2324
path_name <- "report"
25+
report_dir <- file.path(project_dir, "report")
2426
}
2527
init_file <- file.path(project_dir, paste0(".", path_name, "_init.json"))
2628
log4r::debug(
@@ -45,11 +47,8 @@ sync_report_project <- function(project_dir, report_dir_name = NULL) {
4547
.le$logger,
4648
"Grabbing python deps info from options and filesystem"
4749
)
48-
# Grab python dep info
4950
uv_path <- get_uv_path()
5051
args <- get_args(uv_path)
51-
pyvers <- get_py_version(getOption("venv_dir"))
52-
args <- c(args, pyvers)
5352
args_name <- c(
5453
"venv_dir",
5554
"python-docx.version",
@@ -58,6 +57,18 @@ sync_report_project <- function(project_dir, report_dir_name = NULL) {
5857
"uv.version",
5958
"python.version"
6059
)
60+
61+
pyvers <- get_py_version(getOption("venv_dir"))
62+
63+
# Ensure args has a slot for python.version
64+
idx <- match("python.version", args_name)
65+
if (length(args) < idx) {
66+
args <- c(args, rep("", idx - length(args)))
67+
}
68+
69+
# Replace (or set) python.version value
70+
args[idx] <- pyvers
71+
6172
py_version_data <- stats::setNames(as.list(args), args_name)
6273
formatted_deps <- paste0(
6374
names(py_version_data),
@@ -83,17 +94,23 @@ sync_report_project <- function(project_dir, report_dir_name = NULL) {
8394
)
8495
)
8596
log4r::debug(.le$logger, "Calling initialize_python now")
86-
initialize_python(continue = "Y")
97+
metadata_path <- initialize_python(continue = "Y")
8798
update_init_file <- TRUE
99+
100+
if (file.exists(metadata_path) && dir.exists(report_dir)) {
101+
file.copy(
102+
from = metadata_path,
103+
to = file.path(report_dir, basename(metadata_path)),
104+
overwrite = TRUE
105+
)
106+
log4r::debug(.le$logger, "Updating .python_dependency_versions.json in report_dir_name")
107+
}
88108
}
89109
# Check config
90110
log4r::debug(.le$logger, "getting config path now")
91-
if (!is.null(report_dir_name)) {
92-
config_path <- file.path(project_dir, report_dir_name, "config.yaml")
93-
} else {
94-
config_path <- file.path(project_dir, "report", "config.yaml")
95-
}
111+
config_path <- file.path(report_dir, "config.yaml")
96112
log4r::debug(.le$logger, paste0("using config path: ", config_path))
113+
97114
config <- yaml::read_yaml(
98115
config_path,
99116
handlers = list(logical = yaml::verbatim_logical)
@@ -149,12 +166,7 @@ sync_report_project <- function(project_dir, report_dir_name = NULL) {
149166
init$config <- config
150167

151168
json_data <- jsonlite::toJSON(init, pretty = TRUE, auto_unbox = TRUE)
152-
json_with_comment <- paste(
153-
"// WARNING: This file is automatically generated on initialization. Do not edit by hand!",
154-
json_data,
155-
sep = "\n"
156-
)
157-
write(json_with_comment, file = init_file)
169+
write(json_data, file = init_file)
158170
} else {
159171
message("Nothing to do")
160172
}

0 commit comments

Comments
 (0)