Skip to content

Commit a4af6ba

Browse files
committed
Merge branch 'master' into doc-usecases
2 parents 7ae46ab + 214dbea commit a4af6ba

File tree

16 files changed

+322
-114
lines changed

16 files changed

+322
-114
lines changed

.travis.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ matrix:
1717
- REPROMAN_TESTS_SSH=1
1818
- INSTALL_DATALAD=1
1919
- INSTALL_CONDOR=1
20+
- SETUP_SLURM=1
2021
- python: 3.5
2122
env:
2223
- REPROMAN_TESTS_SSH=1
@@ -38,7 +39,7 @@ matrix:
3839
env:
3940
- REPROMAN_LOGLEVEL=2
4041
- REPROMAN_LOGTARGET=/dev/null
41-
- python: 3.4
42+
- python: 3.6
4243
# Note: This this no network run appears to hang or otherwise
4344
# behave oddly on 3.5.
4445
env:
@@ -66,6 +67,9 @@ before_install:
6667
sudo eatmydata tools/ci/prep-travis-forssh-sudo.sh;
6768
tools/ci/prep-travis-forssh.sh;
6869
fi
70+
- if [ ! -z "${SETUP_SLURM:-}" ]; then
71+
tools/ci/setup-slurm-container.sh;
72+
fi
6973
- git config --global user.email "[email protected]"
7074
- git config --global user.name "Travis Almighty"
7175

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ we outline the workflow used by the developers:
113113
Development environment
114114
-----------------------
115115

116-
We support Python 3 (>= 3.4).
116+
We support Python 3 (>= 3.5).
117117

118118
See [README.md:Dependencies](README.md#Dependencies) for basic information
119119
about installation of reproman itself.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internals and/or contributing to the project.
2727

2828
# Installation
2929

30-
ReproMan requires Python 3 (>= 3.4).
30+
ReproMan requires Python 3 (>= 3.5).
3131

3232
## Debian-based systems
3333

reproman/cmd.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,12 @@ def run(self, cmd, log_stdout=True, log_stderr=True, log_online=False,
249249

250250
except Exception as e:
251251
prot_exc = e
252-
lgr.error("Failed to start %r%r: %s" %
253-
(cmd, " under %r" % cwd if cwd else '', exc_str(e)))
252+
if isinstance(e, FileNotFoundError) and expect_fail:
253+
logfn = lgr.debug
254+
else:
255+
logfn = lgr.error
256+
logfn("Failed to start %r%r: %s" %
257+
(cmd, " under %r" % cwd if cwd else '', exc_str(e)))
254258
raise
255259

256260
finally:

reproman/distributions/conda.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,7 @@ def get_miniconda_url(conda_platform, python_version):
8585
raise ValueError("Unsupported platform %s for conda installation" %
8686
conda_platform)
8787
platform += "-x86_64" if ("64" in conda_platform) else "-x86"
88-
# FIXME: We need to update this our conda tracer to work with conda's newer
89-
# than 4.6.14. See gh-443.
90-
return "https://repo.continuum.io/miniconda/Miniconda%s-4.6.14-%s.sh" \
88+
return "https://repo.anaconda.com/miniconda/Miniconda%s-latest-%s.sh" \
9189
% (python_version[0], platform)
9290

9391

@@ -184,22 +182,22 @@ def install_packages(self, session=None):
184182
# TODO: Determine if we can detect miniconda vs anaconad
185183
miniconda_url = get_miniconda_url(self.platform,
186184
self.python_version)
187-
session.execute_command("curl %s -o %s/miniconda.sh" %
188-
(miniconda_url, tmp_dir))
185+
session.execute_command(
186+
"curl --fail --silent --show-error --location "
187+
"--output {}/miniconda.sh {}"
188+
.format(tmp_dir, miniconda_url))
189189
# NOTE: miniconda.sh makes parent directories automatically
190190
session.execute_command("bash -b %s/miniconda.sh -b -p %s" %
191191
(tmp_dir, self.path))
192-
## Update root version of conda
193-
session.execute_command(
194-
"%s/bin/conda install -y conda=%s python=%s" %
195-
(self.path, self.conda_version,
196-
self.get_simple_python_version(self.python_version)))
197-
198-
# Loop through non-root packages, creating the conda-env config
199-
for env in self.environments:
192+
envs = sorted(
193+
self.environments,
194+
# Create/update the root environment before handling anything
195+
# else.
196+
key=lambda x: "_" if x.name == "root" else "_" + x.name)
197+
for env in envs:
200198
export_contents = self.create_conda_export(env)
201199
with make_tempfile(export_contents) as local_config:
202-
remote_config = os.path.join(tmp_dir, env.name)
200+
remote_config = os.path.join(tmp_dir, env.name + ".yaml")
203201
session.put(local_config, remote_config)
204202
if not session.isdir(env.path):
205203
try:
@@ -280,6 +278,7 @@ class CondaTracer(DistributionTracer):
280278

281279
def _init(self):
282280
self._get_conda_env_path = PathRoot(self._is_conda_env_path)
281+
self._get_conda_dist_path = PathRoot(self._is_conda_dist_path)
283282

284283
def _get_packagefields_for_files(self, files):
285284
raise NotImplementedError("TODO")
@@ -335,20 +334,27 @@ def _get_conda_pip_package_details(self, env_export, conda_path):
335334

336335
# If there are pip dependencies, they'll be listed under a
337336
# {"pip": [...]} entry.
338-
pip_pkgs = []
337+
pip_pkgs = set()
339338
for dep in dependencies:
340339
if isinstance(dep, dict) and "pip" in dep:
341340
# Pip packages are recorded in conda exports as "name (loc)",
342341
# "name==version" or "name (loc)==version".
343-
pip_pkgs = [p.split("=")[0].split(" ")[0] for p in dep["pip"]]
342+
pip_pkgs = {p.split("=")[0].split(" ")[0] for p in dep["pip"]}
344343
break
345344

345+
pip = conda_path + "/bin/pip"
346+
if not self._session.exists(pip):
347+
return {}, {}
348+
349+
pkgs_editable = set(piputils.get_pip_packages(
350+
self._session, pip, restriction="editable"))
351+
pip_pkgs.update(pkgs_editable)
352+
346353
if not pip_pkgs:
347354
return {}, {}
348355

349-
pip = conda_path + "/bin/pip"
350356
packages, file_to_package_map = piputils.get_package_details(
351-
self._session, pip, pip_pkgs)
357+
self._session, pip, pip_pkgs, editable_packages=pkgs_editable)
352358
for entry in packages.values():
353359
entry["installer"] = "pip"
354360
return packages, file_to_package_map
@@ -391,6 +397,10 @@ def _get_conda_info(self, conda_path):
391397
def _is_conda_env_path(self, path):
392398
return self._session.exists('%s/conda-meta' % path)
393399

400+
def _is_conda_dist_path(self, path):
401+
return (self._session.exists(path + "/envs")
402+
and self._is_conda_env_path(path))
403+
394404
def identify_distributions(self, paths):
395405
conda_paths = set()
396406
root_to_envs = defaultdict(list)
@@ -417,8 +427,7 @@ def identify_distributions(self, paths):
417427
# Find the root path for the environment
418428
# TODO: cache/memoize for those paths which have been considered
419429
# since will be asked again below
420-
conda_info = self._get_conda_info(conda_path)
421-
root_path = conda_info.get('root_prefix')
430+
root_path = self._get_conda_dist_path(conda_path)
422431
if not root_path:
423432
lgr.warning("Could not find root path for conda environment %s"
424433
% conda_path)

reproman/distributions/piputils.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ def get_pip_packages(session, which_pip, restriction=None):
117117
return (p["name"] for p in json.loads(out))
118118

119119

120-
def get_package_details(session, which_pip, packages=None):
120+
def get_package_details(session, which_pip, packages=None,
121+
editable_packages=None):
121122
"""Get package details from `pip show` and `pip list`.
122123
123124
This is similar to `pip_show`, but it uses `pip list` to get information
@@ -132,6 +133,9 @@ def get_package_details(session, which_pip, packages=None):
132133
packages : list of str, optional
133134
Package names. If not given, all packages returned by `pip list` are
134135
used.
136+
editable_packages : collection of str
137+
If a package name is in this collection, mark it as editable. Passing
138+
this saves a call to `which_pip`.
135139
136140
Returns
137141
-------
@@ -140,8 +144,9 @@ def get_package_details(session, which_pip, packages=None):
140144
"""
141145
if packages is None:
142146
packages = list(get_pip_packages(session, which_pip))
143-
editable_packages = set(
144-
get_pip_packages(session, which_pip, restriction="editable"))
147+
if editable_packages is None:
148+
editable_packages = set(
149+
get_pip_packages(session, which_pip, restriction="editable"))
145150
details, file_to_pkg = pip_show(session, which_pip, packages)
146151

147152
for pkg in details:

reproman/distributions/tests/test_conda.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ def test_get_conda_platform_from_python():
3333

3434
def test_get_miniconda_url():
3535
assert get_miniconda_url("linux-64", "2.7") == \
36-
"https://repo.continuum.io/miniconda/Miniconda2-4.6.14-Linux-x86_64.sh"
36+
"https://repo.anaconda.com/miniconda/Miniconda2-latest-Linux-x86_64.sh"
3737
assert get_miniconda_url("linux-32", "3.4") == \
38-
"https://repo.continuum.io/miniconda/Miniconda3-4.6.14-Linux-x86.sh"
38+
"https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86.sh"
3939
assert get_miniconda_url("osx-64", "3.5.1") == \
40-
"https://repo.continuum.io/miniconda/Miniconda3-4.6.14-MacOSX-x86_64.sh"
40+
"https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh"
4141

4242

4343
def test_get_simple_python_version():
@@ -83,7 +83,7 @@ def test_create_conda_export():
8383
md5=None,
8484
size=None,
8585
url=None,
86-
files=["lib/python2.7/site-packages/rpaths.py"])],
86+
files=["lib/python3.7/site-packages/rpaths.py"])],
8787
channels=[
8888
CondaChannel(
8989
name="conda-forge",
@@ -107,8 +107,8 @@ def test_conda_init_install_and_detect(tmpdir):
107107
dist = CondaDistribution(
108108
name="conda",
109109
path=test_dir,
110-
conda_version="4.3.31",
111-
python_version="2.7.14.final.0",
110+
conda_version="4.8.2",
111+
python_version="3.8.2",
112112
platform=get_conda_platform_from_python(sys.platform) + "-64",
113113
environments=[
114114
CondaEnvironment(
@@ -118,7 +118,7 @@ def test_conda_init_install_and_detect(tmpdir):
118118
CondaPackage(
119119
name="conda",
120120
installer=None,
121-
version="4.3.31",
121+
version="4.8.2",
122122
build=None,
123123
channel_name=None,
124124
md5=None,
@@ -128,7 +128,7 @@ def test_conda_init_install_and_detect(tmpdir):
128128
CondaPackage(
129129
name="pip",
130130
installer=None,
131-
version="9.0.1",
131+
version="20.0.2",
132132
build=None,
133133
channel_name=None,
134134
md5=None,
@@ -159,7 +159,7 @@ def test_conda_init_install_and_detect(tmpdir):
159159
CondaPackage(
160160
name="pip",
161161
installer=None,
162-
version="9.0.1",
162+
version="20.0.2",
163163
build=None,
164164
channel_name=None,
165165
md5=None,
@@ -185,7 +185,7 @@ def test_conda_init_install_and_detect(tmpdir):
185185
md5=None,
186186
size=None,
187187
url=None,
188-
files=["lib/python2.7/site-packages/rpaths.py"])],
188+
files=["lib/python3.8/site-packages/rpaths.py"])],
189189
channels=[
190190
CondaChannel(
191191
name="conda-forge",

reproman/resource/session.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import os.path as op
1818
import re
1919
from shlex import quote as shlex_quote
20+
import subprocess
21+
from ..support.exceptions import CommandError
2022

2123
from reproman.support.exceptions import SessionRuntimeError
2224
from reproman.cmd import Runner
@@ -492,10 +494,20 @@ class POSIXSession(Session):
492494
# -0 is not provided by busybox's env command. So if we decide to make it
493495
# even more portable - something to be done
494496
_GET_ENVIRON_CMD = ['env', '-0']
497+
_ALT_GET_ENVIRON_CMD = ['perl', '-e', r'foreach (keys %ENV) {print "$_=$ENV{$_}\0";}']
495498

496499
@borrowdoc(Session)
497500
def query_envvars(self):
498-
out, err = self.execute_command(self._GET_ENVIRON_CMD)
501+
try:
502+
out, err = self.execute_command(self._GET_ENVIRON_CMD)
503+
except CommandError:
504+
# if this fails, we might need the altenative command...
505+
if self._GET_ENVIRON_CMD == self.__class__._ALT_GET_ENVIRON_CMD:
506+
# ...if it's already installed, we fail...
507+
raise
508+
# ...otherwise we install it and try again
509+
self._GET_ENVIRON_CMD = self._ALT_GET_ENVIRON_CMD
510+
return self.query_envvars()
499511
env = self._parse_envvars_output(out)
500512
# TODO: should we update with our .env or .env_permament????
501513
return env
@@ -577,7 +589,7 @@ def source_script(self, command, permanent=False, diff=True, shell=None):
577589
# just run it and be done
578590
marker = "== =ReproMan == =" # unique marker to be able to split away
579591
# possible output from the sourced script
580-
get_env_command = " ".join('"%s"' % s for s in self._GET_ENVIRON_CMD)
592+
get_env_command = " ".join("'%s'" % s for s in self._GET_ENVIRON_CMD)
581593
shell = shell or self.query_envvars().get('SHELL', None)
582594
if not isinstance(command, list):
583595
command = [command]

reproman/support/external_versions.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,32 @@ def __cmp__(self, other):
4444
_runner = Runner()
4545

4646

47+
def _try_run(cmd):
48+
return _runner.run(cmd, expect_fail=True, expect_stderr=True)
49+
50+
4751
def _get_annex_version():
4852
"""Return version of available git-annex"""
49-
return _runner.run('git annex version --raw'.split())[0]
53+
return _try_run(['git', 'annex', 'version', '--raw'])[0]
5054

5155

5256
def _get_git_version():
5357
"""Return version of available git"""
54-
return _runner.run('git version'.split())[0].split()[-1]
58+
out = _try_run(['git', 'version'])[0]
59+
return out.split()[-1]
5560

5661

5762
def _get_apt_cache_version():
5863
"""Return version of available apt-cache."""
59-
return _runner.run('apt-cache -v'.split())[0].split()[1]
64+
out = _try_run(['apt-cache', '-v'])[0]
65+
return out.split()[1]
6066

6167

6268
def _get_system_ssh_version():
6369
"""Return version of ssh available system-wide
6470
"""
6571
try:
66-
out, err = _runner.run('ssh -V'.split(),
67-
expect_fail=True, expect_stderr=True)
72+
out, err = _try_run(['ssh', '-V'])
6873
# apparently spits out to err but I wouldn't trust it blindly
6974
if err.startswith('OpenSSH'):
7075
out = err
@@ -80,7 +85,7 @@ def _get_singularity_version():
8085
# example output:
8186
# "singularity version 3.0.3+ds"
8287
# "2.6.1-dist"
83-
out = _runner.run(["singularity", "--version"])[0]
88+
out = _try_run(["singularity", "--version"])[0]
8489
return out.split(' ')[-1].split("-")[0].split("+")[0]
8590

8691

@@ -90,15 +95,17 @@ def _get_svn_version():
9095
#
9196
# svn, version 1.9.5 (r1770682)
9297
# [...]
93-
return _runner.run(["svn", "--version"])[0].split()[2]
98+
out = _try_run(["svn", "--version"])[0]
99+
return out.split()[2]
94100

95101

96102
def _get_condor_version():
97103
"""Return version of available condor"""
98104
# Example output:
99105
#
100106
# $CondorVersion: 8.6.8 Nov 30 2017 BuildID: [...]
101-
return _runner.run(['condor_version'])[0].split()[1]
107+
out = _try_run(['condor_version'])[0]
108+
return out.split()[1]
102109

103110

104111
class ExternalVersions(object):

0 commit comments

Comments
 (0)