Skip to content

Commit 5e67e3c

Browse files
authored
Support .binder directory (#653)
Support .binder directory
2 parents 9099def + 6de4c5e commit 5e67e3c

File tree

8 files changed

+87
-33
lines changed

8 files changed

+87
-33
lines changed

docs/source/design.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ The philosophy for the repo2docker buildpacks includes:
77
- using common configuration files for familiar installation and packaging tools
88
- allowing configuration files to be combined to compose more complex setups
99
- specifying default locations for configuration files
10-
(the repository's root directory or .binder directory)
10+
(in the repository's root, `binder` or `.binder` directory)
1111

1212

1313
When designing `repo2docker` and adding to it in the future, the

docs/source/usage.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,14 @@ specify the ``branch-name`` or ``commit-hash``. For example::
7272
Where to put configuration files
7373
================================
7474

75-
``repo2docker`` will look for configuration files in either:
75+
``repo2docker`` will look for configuration files in:
7676

7777
* A folder named ``binder/`` in the root of the repository.
78+
* A folder named ``.binder/`` in the root of the repository.
7879
* The root directory of the repository.
7980

80-
If the folder ``binder/`` is located at the top level of the repository,
81-
only configuration files in the ``binder/`` folder will be considered.
81+
`repo2docker` searches for these folders in order (``binder/``, ``.binder/``,
82+
root). Only configuration files in the first identified folder are considered.
8283

8384
Check the complete list of :ref:`configuration files <config-files>` supported
8485
by ``repo2docker`` to see how to configure the build process.

repo2docker/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from . import __version__
88
from .utils import validate_and_generate_port_mapping, is_valid_docker_image_name
99

10+
1011
def validate_image_name(image_name):
1112
"""
1213
Validate image_name read by argparse

repo2docker/buildpacks/base.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,12 +423,26 @@ def get_start_script(self):
423423
"""
424424
return None
425425

426+
@property
427+
def binder_dir(self):
428+
has_binder = os.path.isdir("binder")
429+
has_dotbinder = os.path.isdir(".binder")
430+
431+
if has_binder and has_dotbinder:
432+
raise RuntimeError(
433+
"The repository contains both a 'binder' and a '.binder' "
434+
"directory. However they are exclusive.")
435+
436+
if has_dotbinder:
437+
return ".binder"
438+
elif has_binder:
439+
return "binder"
440+
else:
441+
return ""
442+
426443
def binder_path(self, path):
427444
"""Locate a file"""
428-
if os.path.exists('binder'):
429-
return os.path.join('binder', path)
430-
else:
431-
return path
445+
return os.path.join(self.binder_dir, path)
432446

433447
def detect(self):
434448
return True

repo2docker/buildpacks/nix/nix-shell-wrapper

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,36 @@ trap _term SIGTERM
1010

1111
# if there is a binder/ sub-directory it takes precedence
1212
# files outside it are ignored
13-
if [ -e ./binder ]; then
14-
nixpath="./binder/default.nix";
15-
if [ -f ./binder/start ]; then
16-
chmod u+x ./binder/start
17-
# Using `$@`` here which is what the internet recommends leads to
18-
# errors when the command is something like `jupyter --ip=0.0.0.0 ...`
19-
# as nix-shell picks that up as an argument to it instead of the command.
20-
# There are several issues on the nix repos discussing this and adding support
21-
# for -- to indicate "all arguments after this are for the command, not nix-shell"
22-
# but it seems they have stalled/not yet produced an implementation.
23-
# So let's use `$*` for now.
24-
nix-shell $nixpath --command "./binder/start $*" &
25-
else
26-
nix-shell $nixpath --command "$*" &
13+
# find binder sub-directory (if present)
14+
binder_dir="./"
15+
for dir in "./binder" "./.binder" ; do
16+
if [ -e $dir ]; then
17+
binder_dir=$dir
18+
break
2719
fi
20+
done
21+
22+
# raise error if both binder and .binder are found
23+
if [[ -d "./binder" && -d "./.binder" ]]; then
24+
echo "Error: Found both binder and .binder directories."
25+
exit 1
26+
fi
27+
28+
echo "binder_dir is: $binder_dir"
29+
30+
nixpath="$binder_dir/default.nix";
31+
if [ -f $binder_dir/start ]; then
32+
chmod u+x $binder_dir/start
33+
# Using `$@`` here which is what the internet recommends leads to
34+
# errors when the command is something like `jupyter --ip=0.0.0.0 ...`
35+
# as nix-shell picks that up as an argument to it instead of the command.
36+
# There are several issues on the nix repos discussing this and adding support
37+
# for -- to indicate "all arguments after this are for the command, not nix-shell"
38+
# but it seems they have stalled/not yet produced an implementation.
39+
# So let's use `$*` for now.
40+
nix-shell $nixpath --command "$binder_dir/start $*" &
2841
else
29-
nixpath="./default.nix";
30-
if [ -f ./start ]; then
31-
chmod u+x ./start
32-
nix-shell $nixpath --command "./start $*" &
33-
else
34-
nix-shell $nixpath --command "$*" &
35-
fi
42+
nix-shell $nixpath --command "$*" &
3643
fi
3744

3845
PID=$!

repo2docker/buildpacks/python/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def get_assemble_scripts(self):
6767
))
6868

6969
# setup.py exists *and* binder dir is not used
70-
if not os.path.exists('binder') and os.path.exists(setup_py):
70+
if not self.binder_dir and os.path.exists(setup_py):
7171
assemble_scripts.append((
7272
'${NB_USER}',
7373
'{} install --no-cache-dir .'.format(pip)
@@ -88,6 +88,6 @@ def detect(self):
8888
return True
8989
else:
9090
return False
91-
if not os.path.exists('binder') and os.path.exists(setup_py):
91+
if not self.binder_dir and os.path.exists(setup_py):
9292
return True
9393
return os.path.exists(requirements_txt)

repo2docker/buildpacks/r.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def detect(self):
8484
return True
8585

8686
description_R = 'DESCRIPTION'
87-
if ((not os.path.exists('binder') and os.path.exists(description_R))
87+
if ((not self.binder_dir and os.path.exists(description_R))
8888
or 'r' in self.stencila_contexts):
8989
if not self.checkpoint_date:
9090
# no R snapshot date set through runtime.txt
@@ -300,7 +300,7 @@ def get_assemble_scripts(self):
300300
]
301301

302302
description_R = 'DESCRIPTION'
303-
if not os.path.exists('binder') and os.path.exists(description_R):
303+
if not self.binder_dir and os.path.exists(description_R):
304304
assemble_scripts += [
305305
(
306306
"${NB_USER}",

tests/unit/test_binder_dir.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import os
2+
3+
import pytest
4+
5+
from repo2docker import buildpacks
6+
7+
8+
@pytest.mark.parametrize("binder_dir", ['.binder', 'binder'])
9+
def test_binder_dir_property(tmpdir, binder_dir):
10+
tmpdir.chdir()
11+
os.mkdir(binder_dir)
12+
13+
bp = buildpacks.BuildPack()
14+
assert binder_dir in bp.binder_dir
15+
assert bp.binder_path('foo.yaml') == os.path.join(binder_dir, 'foo.yaml')
16+
17+
18+
def test_root_binder_dir(tmpdir):
19+
tmpdir.chdir()
20+
bp = buildpacks.BuildPack()
21+
assert bp.binder_dir == ''
22+
23+
24+
def test_exclusive_binder_dir(tmpdir):
25+
tmpdir.chdir()
26+
os.mkdir('./binder')
27+
os.mkdir('./.binder')
28+
29+
bp = buildpacks.BuildPack()
30+
with pytest.raises(RuntimeError):
31+
_ = bp.binder_dir

0 commit comments

Comments
 (0)