Skip to content

Commit 06aea3a

Browse files
Refactor R installation checks and commands
1 parent 55adf47 commit 06aea3a

1 file changed

Lines changed: 140 additions & 91 deletions

File tree

‎setup.py‎

Lines changed: 140 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,197 @@
1+
import os
12
import platform
23
import subprocess
4+
import sys
5+
6+
7+
def _running_noninteractively():
8+
"""True when there is no human at a terminal to answer a prompt.
9+
10+
This covers CI systems (GitHub Actions sets CI=true), any environment
11+
where CI/PIP_NO_INPUT is set, and plain non-tty stdin.
12+
"""
13+
if os.environ.get("CI") or os.environ.get("PIP_NO_INPUT"):
14+
return True
15+
try:
16+
return not sys.stdin.isatty()
17+
except Exception:
18+
return True
19+
20+
21+
def _is_packaging_only_invocation():
22+
"""True when setup.py is only being asked to build metadata/artifacts
23+
(sdist, bdist_wheel, egg_info, ...), not to actually install the
24+
package. These commands run on build machines (e.g. CI building a
25+
release) that don't need R/rpy2 present at all -- R is a *runtime*
26+
dependency of the installed package, not a build-time one.
27+
"""
28+
packaging_commands = {
29+
"sdist", "bdist_wheel", "bdist", "egg_info", "dist_info",
30+
"check", "--version", "--help", "--help-commands",
31+
}
32+
return any(arg in packaging_commands for arg in sys.argv[1:])
33+
334

435
def check_r_installed():
536
current_platform = platform.system()
637

738
if current_platform == "Windows":
8-
# Check if R is installed on Windows by checking the registry
939
try:
1040
subprocess.run(
1141
["reg", "query", "HKLM\\Software\\R-core\\R"], check=True
1242
)
1343
print("R is already installed on Windows.")
1444
return True
15-
except subprocess.CalledProcessError:
45+
except (subprocess.CalledProcessError, FileNotFoundError):
1646
print("R is not installed on Windows.")
1747
return False
1848

19-
elif current_platform == "Linux":
20-
# Check if R is installed on Linux by checking if the 'R' executable is available
49+
elif current_platform in ("Linux", "Darwin"):
2150
try:
2251
subprocess.run(["which", "R"], check=True)
23-
print("R is already installed on Linux.")
52+
print(f"R is already installed on {current_platform}.")
2453
return True
25-
except subprocess.CalledProcessError:
26-
print("R is not installed on Linux.")
27-
return False
28-
29-
elif current_platform == "Darwin": # macOS
30-
# Check if R is installed on macOS by checking if the 'R' executable is available
31-
try:
32-
subprocess.run(["which", "R"], check=True)
33-
print("R is already installed on macOS.")
34-
return True
35-
except subprocess.CalledProcessError:
36-
print("R is not installed on macOS.")
54+
except (subprocess.CalledProcessError, FileNotFoundError):
55+
print(f"R is not installed on {current_platform}.")
3756
return False
3857

3958
else:
4059
print("Unsupported platform. Unable to check for R installation.")
4160
return False
4261

43-
def install_r():
4462

63+
def install_r():
4564
current_platform = platform.system()
4665

4766
if current_platform == "Windows":
48-
# Install R on Windows using PowerShell
49-
install_command = "Start-Process powershell -Verb subprocess.runAs -ArgumentList '-Command \"& {Invoke-WebRequest https://cran.r-project.org/bin/windows/base/R-4.1.2-win.exe -OutFile R.exe}; Start-Process R.exe -ArgumentList '/SILENT' -Wait}'"
67+
install_command = (
68+
"Start-Process powershell -Verb runAs -ArgumentList "
69+
"'-Command \"& {Invoke-WebRequest "
70+
"https://cran.r-project.org/bin/windows/base/R-4.1.2-win.exe "
71+
"-OutFile R.exe}; Start-Process R.exe -ArgumentList "
72+
"'/SILENT' -Wait}'"
73+
)
5074
subprocess.run(install_command, shell=True)
5175

5276
elif current_platform == "Linux":
53-
# Install R on Linux using the appropriate package manager (e.g., apt-get)
5477
install_command = (
55-
"sudo apt update -qq && sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9"
56-
+ "&& sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu focal-cran40/'"
57-
+ "&& sudo apt update"
58-
+ "&& sudo apt -y install r-base"
78+
"sudo apt update -qq && "
79+
"sudo apt-key adv --keyserver keyserver.ubuntu.com "
80+
"--recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9 && "
81+
"sudo add-apt-repository 'deb https://cloud.r-project.org/bin/linux/ubuntu focal-cran40/' && "
82+
"sudo apt update && "
83+
"sudo apt -y install r-base"
5984
)
6085
subprocess.run(install_command, shell=True)
6186

62-
elif current_platform == "Darwin": # macOS
63-
# Install R on macOS using Homebrew
64-
install_command = "brew install r"
65-
subprocess.run(install_command, shell=True)
87+
elif current_platform == "Darwin":
88+
subprocess.run("brew install r", shell=True)
6689

6790
else:
68-
6991
print("Unsupported platform. Unable to install R.")
7092

93+
7194
def install_packages():
72-
try:
73-
subprocess.run(["Rscript", "-e", "utils::install.packages('remotes', dependencies=TRUE)"])
74-
subprocess.run(["Rscript", "-e", "utils::install.packages(c('R6', 'Rcpp', 'skimr'), dependencies=TRUE)"])
75-
subprocess.run(["Rscript", "-e", "remotes::install_github('Techtonique/learningmachine')"])
76-
except subprocess.CalledProcessError as e:
77-
print(f"Error occurred: {e}")
78-
print(f"Return code: {e.returncode}")
79-
print(f"Output: {e.output}")
80-
print(f"Stderr: {e.stderr}")
95+
"""Install the R-side dependencies, retrying into a local library
96+
('r-learningmachine') if the default library isn't writable."""
97+
attempts = [
98+
{"lib": None},
99+
{"lib": "r-learningmachine"},
100+
]
101+
102+
for attempt in attempts:
103+
lib = attempt["lib"]
104+
lib_arg = f", lib='{lib}'" if lib else ""
81105
try:
82-
subprocess.run(["mkdir", "-p", "r-learningmachine"])
83-
subprocess.run(["Rscript", "-e", "utils::install.packages('remotes', lib='r-learningmachine', dependencies=TRUE)"])
84-
subprocess.run(["Rscript", "-e", "utils::install.packages(c('R6', 'Rcpp', 'skimr'), lib='r-learningmachine', dependencies=TRUE)"])
85-
subprocess.run(["Rscript", "-e", "remotes::install_github('Techtonique/learningmachine', lib='r-learningmachine')"])
86-
except subprocess.CalledProcessError as e:
87-
print(f"Error occurred: {e}")
106+
if lib:
107+
subprocess.run(["mkdir", "-p", lib], check=True)
108+
subprocess.run(
109+
["Rscript", "-e",
110+
f"utils::install.packages('remotes', dependencies=TRUE{lib_arg})"],
111+
check=True,
112+
)
113+
subprocess.run(
114+
["Rscript", "-e",
115+
f"utils::install.packages(c('R6', 'Rcpp', 'skimr'), dependencies=TRUE{lib_arg})"],
116+
check=True,
117+
)
118+
subprocess.run(
119+
["Rscript", "-e",
120+
f"remotes::install_github('Techtonique/learningmachine'{lib_arg})"],
121+
check=True,
122+
)
123+
print(f"R package installation succeeded (lib={lib!r}).")
124+
return True
125+
except subprocess.CalledProcessError as e:
126+
print(f"Error occurred while installing R packages (lib={lib!r}): {e}")
88127
print(f"Return code: {e.returncode}")
89-
print(f"Output: {e.output}")
90-
print(f"Stderr: {e.stderr}")
91-
try:
92-
subprocess.run(["Rscript", "-e", "utils::install.packages('remotes', dependencies=TRUE)"])
93-
subprocess.run(["Rscript", "-e", "utils::install.packages(c('R6', 'Rcpp', 'skimr'), dependencies=TRUE)"])
94-
subprocess.run(["Rscript", "-e", "remotes::install_github('Techtonique/learningmachine')"])
95-
except subprocess.CalledProcessError as e:
96-
print(f"Error occurred: {e}")
97-
print(f"Return code: {e.returncode}")
98-
print(f"Output: {e.output}")
99-
print(f"Stderr: {e.stderr}")
100-
try:
101-
subprocess.run(["mkdir", "-p", "r-learningmachine"])
102-
subprocess.run(["Rscript", "-e", "utils::install.packages('remotes', lib='r-learningmachine', dependencies=TRUE)"])
103-
subprocess.run(["Rscript", "-e", "utils::install.packages(c('R6', 'Rcpp', 'skimr'), lib='r-learningmachine', dependencies=TRUE)"])
104-
subprocess.run(["Rscript", "-e", "remotes::install_github('Techtonique/learningmachine', lib='r-learningmachine')"])
105-
except subprocess.CalledProcessError as e:
106-
print(f"Error occurred: {e}")
107-
print(f"Return code: {e.returncode}")
108-
print(f"Output: {e.output}")
109-
print(f"Stderr: {e.stderr}")
110-
try:
111-
subprocess.run(["Rscript", "-e", "utils::install.packages('remotes', dependencies=TRUE)"])
112-
subprocess.run(["Rscript", "-e", "utils::install.packages(c('R6', 'Rcpp', 'skimr'), dependencies=TRUE)"])
113-
subprocess.run(["Rscript", "-e", "remotes::install_github('Techtonique/learningmachine')"])
114-
except subprocess.CalledProcessError as e:
115-
print(f"Error occurred: {e}")
116-
print(f"Return code: {e.returncode}")
117-
print(f"Output: {e.output}")
118-
print(f"Stderr: {e.stderr}")
119-
subprocess.run(["mkdir", "-p", "r-learningmachine"])
120-
subprocess.run(["Rscript", "-e", "utils::install.packages('remotes', lib='r-learningmachine', dependencies=TRUE)"])
121-
subprocess.run(["Rscript", "-e", "utils::install.packages(c('R6', 'Rcpp', 'skimr'), lib='r-learningmachine', dependencies=TRUE)"])
122-
subprocess.run(["Rscript", "-e", "remotes::install_github('Techtonique/learningmachine', lib='r-learningmachine')"])
123-
124-
125-
# Check if R is installed; if not, install it
126-
if not check_r_installed():
127-
install_r_prompt = int(input("Try installing R? 1-yes, 2-no"))
128-
if install_r_prompt == 1:
128+
129+
print(
130+
"Warning: could not install the R 'learningmachine' package "
131+
"automatically. Install it manually, e.g.:\n"
132+
" Rscript -e \"remotes::install_github('Techtonique/learningmachine')\""
133+
)
134+
return False
135+
136+
137+
def ensure_r_available():
138+
"""Make sure R is present, installing it (or asking to) only when it
139+
actually makes sense to do so."""
140+
if check_r_installed():
141+
print("No R installation needed.")
142+
return
143+
144+
if _running_noninteractively():
145+
if os.environ.get("LEARNINGMACHINE_AUTO_INSTALL_R") == "1":
146+
print("Non-interactive environment: auto-installing R "
147+
"(LEARNINGMACHINE_AUTO_INSTALL_R=1 was set).")
148+
install_r()
149+
else:
150+
print(
151+
"R is not installed and this looks like a non-interactive "
152+
"environment (CI or no TTY), so setup.py will NOT prompt "
153+
"for input and will NOT attempt to install R automatically.\n"
154+
"Set the environment variable LEARNINGMACHINE_AUTO_INSTALL_R=1 "
155+
"before running setup.py if you want it to try installing R, "
156+
"or install R yourself first: https://cloud.r-project.org/"
157+
)
158+
return
159+
160+
# Interactive session with a real human at a terminal.
161+
try:
162+
install_r_prompt = int(input("Try installing R? 1-yes, 2-no: "))
163+
except (EOFError, ValueError):
164+
install_r_prompt = 2
165+
166+
if install_r_prompt == 1:
129167
print("Installing R...")
130168
install_r()
131169
else:
132-
raise ValueError('Try installing R manually first.')
133-
else:
134-
print("No R installation needed.")
170+
print(
171+
"Skipping R installation. Install R manually first: "
172+
"https://cloud.r-project.org/"
173+
)
135174

136-
install_packages()
137175

138-
subprocess.run(["pip", "install", "rpy2"])
176+
# Building an sdist/wheel (e.g. in CI to publish a release) never needs R
177+
# or rpy2 on the build machine -- those are runtime dependencies for
178+
# whoever installs the package. Only run the R setup dance for actual
179+
# install-type invocations.
180+
if not _is_packaging_only_invocation():
181+
ensure_r_available()
182+
if check_r_installed():
183+
install_packages()
184+
subprocess.run([sys.executable, "-m", "pip", "install", "rpy2"])
185+
else:
186+
print(f"Packaging-only invocation ({' '.join(sys.argv[1:])}); "
187+
"skipping R/rpy2 setup.")
139188

140189
from setuptools import setup, find_packages
141190
from codecs import open
142191
from os import path
143192

144193
# 4 - Package setup -----------------------------------------------
145-
194+
146195
"""The setup script."""
147196

148197
setup(

0 commit comments

Comments
 (0)