Skip to content

Commit 10e9e14

Browse files
alalazobecker33
andauthored
Bootstrap clingo from sources (#21446)
* Allow the bootstrapping of clingo from sources Allow python builds with system python as external for MacOS * Ensure consistent configuration when bootstrapping clingo This commit uses context managers to ensure we can bootstrap clingo using a consistent configuration regardless of the use case being managed. * Github actions: test clingo with bootstrapping from sources * Add command to inspect and clean the bootstrap store Prevent users to set the install tree root to the bootstrap store * clingo: documented how to bootstrap from sources Co-authored-by: Gregory Becker <becker33@llnl.gov>
1 parent 6d54df1 commit 10e9e14

12 files changed

Lines changed: 359 additions & 44 deletions

File tree

.github/workflows/linux_unit_tests.yaml

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
strategy:
1616
matrix:
1717
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
18+
concretizer: ['original', 'clingo']
1819

1920
steps:
2021
- uses: actions/checkout@v2
@@ -50,16 +51,23 @@ jobs:
5051
mkdir -p ${KCOV_ROOT}/build
5152
cd ${KCOV_ROOT}/build && cmake -Wno-dev ${KCOV_ROOT}/kcov-${KCOV_VERSION} && cd -
5253
make -C ${KCOV_ROOT}/build && sudo make -C ${KCOV_ROOT}/build install
54+
- name: Bootstrap clingo from sources
55+
if: ${{ matrix.concretizer == 'clingo' }}
56+
run: |
57+
. share/spack/setup-env.sh
58+
spack external find --not-buildable cmake bison
59+
spack -v solve zlib
5360
- name: Run unit tests
5461
env:
5562
COVERAGE: true
63+
SPACK_TEST_SOLVER: ${{ matrix.concretizer }}
5664
run: |
5765
share/spack/qa/run-unit-tests
5866
coverage combine
5967
coverage xml
6068
- uses: codecov/codecov-action@v1
6169
with:
62-
flags: unittests,linux
70+
flags: unittests,linux,${{ matrix.concretizer }}
6371
shell:
6472
runs-on: ubuntu-latest
6573
steps:
@@ -143,28 +151,6 @@ jobs:
143151
run: |
144152
source share/spack/setup-env.sh
145153
spack unit-test -k 'not svn and not hg' -x --verbose
146-
147-
clingo:
148-
# Test for the clingo based solver
149-
runs-on: ubuntu-latest
150-
container: spack/github-actions:clingo
151-
steps:
152-
- name: Run unit tests
153-
run: |
154-
whoami && echo PWD=$PWD && echo HOME=$HOME && echo SPACK_TEST_SOLVER=$SPACK_TEST_SOLVER
155-
which clingo && clingo --version
156-
git clone https://github.com/spack/spack.git && cd spack
157-
git fetch origin ${{ github.ref }}:test-branch
158-
git checkout test-branch
159-
. share/spack/setup-env.sh
160-
spack compiler find
161-
spack solve mpileaks%gcc
162-
coverage run $(which spack) unit-test -v
163-
coverage combine
164-
coverage xml
165-
- uses: codecov/codecov-action@v1
166-
with:
167-
flags: unittests,linux,clingo
168154
clingo-cffi:
169155
# Test for the clingo based solver (using clingo-cffi)
170156
runs-on: ubuntu-latest

lib/spack/docs/getting_started.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,53 @@ environment*, especially for ``PATH``. Only software that comes with
111111
the system, or that you know you wish to use with Spack, should be
112112
included. This procedure will avoid many strange build errors.
113113

114+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
115+
Optional: Bootstrapping clingo
116+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
117+
118+
Spack supports using clingo as an external solver to compute which software
119+
needs to be installed. If you have a default compiler supporting C++14 Spack
120+
can automatically bootstrap this tool from sources the first time it is
121+
needed:
122+
123+
.. code-block:: console
124+
125+
$ spack solve zlib
126+
[+] /usr (external bison-3.0.4-wu5pgjchxzemk5ya2l3ddqug2d7jv6eb)
127+
[+] /usr (external cmake-3.19.4-a4kmcfzxxy45mzku4ipmj5kdiiz5a57b)
128+
[+] /usr (external python-3.6.9-x4fou4iqqlh5ydwddx3pvfcwznfrqztv)
129+
==> Installing re2c-1.2.1-e3x6nxtk3ahgd63ykgy44mpuva6jhtdt
130+
[ ... ]
131+
==> Optimization: [0, 0, 0, 0, 0, 1, 0, 0, 0]
132+
zlib@1.2.11%gcc@10.1.0+optimize+pic+shared arch=linux-ubuntu18.04-broadwell
133+
134+
If you want to speed-up bootstrapping, you may try to search for ``cmake`` and ``bison``
135+
on your system:
136+
137+
.. code-block:: console
138+
139+
$ spack external find cmake bison
140+
==> The following specs have been detected on this system and added to /home/spack/.spack/packages.yaml
141+
bison@3.0.4 cmake@3.19.4
142+
143+
All the tools Spack needs for its own functioning are installed in a separate store, which lives
144+
under the ``${HOME}/.spack`` directory. The software installed there can be queried with:
145+
146+
.. code-block:: console
147+
148+
$ spack find --bootstrap
149+
==> Showing internal bootstrap store at "/home/spack/.spack/bootstrap/store"
150+
==> 3 installed packages
151+
-- linux-ubuntu18.04-x86_64 / gcc@10.1.0 ------------------------
152+
clingo-bootstrap@spack python@3.6.9 re2c@1.2.1
153+
154+
In case it's needed the bootstrap store can also be cleaned with:
155+
156+
.. code-block:: console
157+
158+
$ spack clean -b
159+
==> Removing software in "/home/spack/.spack/bootstrap/store"
160+
114161
^^^^^^^^^^^^^^^^^^^^^^^^^^
115162
Optional: Alternate Prefix
116163
^^^^^^^^^^^^^^^^^^^^^^^^^^

lib/spack/spack/bootstrap.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other
2+
# Spack Project Developers. See the top-level COPYRIGHT file for details.
3+
#
4+
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
5+
import contextlib
6+
import os
7+
import sys
8+
9+
import llnl.util.filesystem as fs
10+
import llnl.util.tty as tty
11+
12+
import spack.architecture
13+
import spack.config
14+
import spack.paths
15+
import spack.repo
16+
import spack.spec
17+
import spack.store
18+
import spack.user_environment as uenv
19+
import spack.util.executable
20+
from spack.util.environment import EnvironmentModifications
21+
22+
23+
@contextlib.contextmanager
24+
def spack_python_interpreter():
25+
"""Override the current configuration to set the interpreter under
26+
which Spack is currently running as the only Python external spec
27+
available.
28+
"""
29+
python_cls = type(spack.spec.Spec('python').package)
30+
python_prefix = os.path.dirname(os.path.dirname(sys.executable))
31+
externals = python_cls.determine_spec_details(
32+
python_prefix, [os.path.basename(sys.executable)])
33+
external_python = externals[0]
34+
35+
entry = {
36+
'buildable': False,
37+
'externals': [
38+
{'prefix': python_prefix, 'spec': str(external_python)}
39+
]
40+
}
41+
42+
with spack.config.override('packages:python::', entry):
43+
yield
44+
45+
46+
def make_module_available(module, spec=None, install=False):
47+
"""Ensure module is importable"""
48+
# If we already can import it, that's great
49+
try:
50+
__import__(module)
51+
return
52+
except ImportError:
53+
pass
54+
55+
# If it's already installed, use it
56+
# Search by spec
57+
spec = spack.spec.Spec(spec or module)
58+
59+
# We have to run as part of this python
60+
# We can constrain by a shortened version in place of a version range
61+
# because this spec is only used for querying or as a placeholder to be
62+
# replaced by an external that already has a concrete version. This syntax
63+
# is not suffucient when concretizing without an external, as it will
64+
# concretize to python@X.Y instead of python@X.Y.Z
65+
spec.constrain('^python@%d.%d' % sys.version_info[:2])
66+
installed_specs = spack.store.db.query(spec, installed=True)
67+
68+
for ispec in installed_specs:
69+
# TODO: make sure run-environment is appropriate
70+
module_path = os.path.join(ispec.prefix,
71+
ispec['python'].package.site_packages_dir)
72+
module_path_64 = module_path.replace('/lib/', '/lib64/')
73+
try:
74+
sys.path.append(module_path)
75+
sys.path.append(module_path_64)
76+
__import__(module)
77+
return
78+
except ImportError:
79+
tty.warn("Spec %s did not provide module %s" % (ispec, module))
80+
sys.path = sys.path[:-2]
81+
82+
def _raise_error(module_name, module_spec):
83+
error_msg = 'cannot import module "{0}"'.format(module_name)
84+
if module_spec:
85+
error_msg += ' from spec "{0}'.format(module_spec)
86+
raise ImportError(error_msg)
87+
88+
if not install:
89+
_raise_error(module, spec)
90+
91+
with spack_python_interpreter():
92+
# We will install for ourselves, using this python if needed
93+
# Concretize the spec
94+
spec.concretize()
95+
spec.package.do_install()
96+
97+
module_path = os.path.join(spec.prefix,
98+
spec['python'].package.site_packages_dir)
99+
module_path_64 = module_path.replace('/lib/', '/lib64/')
100+
try:
101+
sys.path.append(module_path)
102+
sys.path.append(module_path_64)
103+
__import__(module)
104+
return
105+
except ImportError:
106+
sys.path = sys.path[:-2]
107+
_raise_error(module, spec)
108+
109+
110+
def get_executable(exe, spec=None, install=False):
111+
"""Find an executable named exe, either in PATH or in Spack
112+
113+
Args:
114+
exe (str): needed executable name
115+
spec (Spec or str): spec to search for exe in (default exe)
116+
install (bool): install spec if not available
117+
118+
When ``install`` is True, Spack will use the python used to run Spack as an
119+
external. The ``install`` option should only be used with packages that
120+
install quickly (when using external python) or are guaranteed by Spack
121+
organization to be in a binary mirror (clingo)."""
122+
# Search the system first
123+
runner = spack.util.executable.which(exe)
124+
if runner:
125+
return runner
126+
127+
# Check whether it's already installed
128+
spec = spack.spec.Spec(spec or exe)
129+
installed_specs = spack.store.db.query(spec, installed=True)
130+
for ispec in installed_specs:
131+
# filter out directories of the same name as the executable
132+
exe_path = [exe_p for exe_p in fs.find(ispec.prefix, exe)
133+
if fs.is_exe(exe_p)]
134+
if exe_path:
135+
ret = spack.util.executable.Executable(exe_path[0])
136+
envmod = EnvironmentModifications()
137+
for dep in ispec.traverse(root=True, order='post'):
138+
envmod.extend(uenv.environment_modifications_for_spec(dep))
139+
ret.add_default_envmod(envmod)
140+
return ret
141+
else:
142+
tty.warn('Exe %s not found in prefix %s' % (exe, ispec.prefix))
143+
144+
def _raise_error(executable, exe_spec):
145+
error_msg = 'cannot find the executable "{0}"'.format(executable)
146+
if exe_spec:
147+
error_msg += ' from spec "{0}'.format(exe_spec)
148+
raise RuntimeError(error_msg)
149+
150+
# If we're not allowed to install this for ourselves, we can't find it
151+
if not install:
152+
_raise_error(exe, spec)
153+
154+
with spack_python_interpreter():
155+
# We will install for ourselves, using this python if needed
156+
# Concretize the spec
157+
spec.concretize()
158+
159+
spec.package.do_install()
160+
# filter out directories of the same name as the executable
161+
exe_path = [exe_p for exe_p in fs.find(spec.prefix, exe)
162+
if fs.is_exe(exe_p)]
163+
if exe_path:
164+
ret = spack.util.executable.Executable(exe_path[0])
165+
envmod = EnvironmentModifications()
166+
for dep in spec.traverse(root=True, order='post'):
167+
envmod.extend(uenv.environment_modifications_for_spec(dep))
168+
ret.add_default_envmod(envmod)
169+
return ret
170+
171+
_raise_error(exe, spec)
172+
173+
174+
@contextlib.contextmanager
175+
def ensure_bootstrap_configuration():
176+
# Default configuration scopes excluding command
177+
# line and builtin
178+
config_scopes = [
179+
spack.config.ConfigScope(name, path)
180+
for name, path in spack.config.configuration_paths
181+
]
182+
183+
with spack.architecture.use_platform(spack.architecture.real_platform()):
184+
with spack.config.use_configuration(*config_scopes):
185+
with spack.repo.use_repositories(spack.paths.packages_path):
186+
with spack.store.use_store(spack.paths.user_bootstrap_store):
187+
with spack_python_interpreter():
188+
yield

lib/spack/spack/build_systems/python.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,28 @@ def install_args(self, spec, prefix):
243243
if ('py-setuptools' == spec.name or # this is setuptools, or
244244
'py-setuptools' in spec._dependencies and # it's an immediate dep
245245
'build' in spec._dependencies['py-setuptools'].deptypes):
246-
args += ['--single-version-externally-managed', '--root=/']
246+
args += ['--single-version-externally-managed']
247+
248+
# Get all relative paths since we set the root to `prefix`
249+
# We query the python with which these will be used for the lib and inc
250+
# directories. This ensures we use `lib`/`lib64` as expected by python.
251+
python = spec['python'].package.command
252+
command_start = 'print(distutils.sysconfig.'
253+
commands = ';'.join([
254+
'import distutils.sysconfig',
255+
command_start + 'get_python_lib(plat_specific=False, prefix=""))',
256+
command_start + 'get_python_lib(plat_specific=True, prefix=""))',
257+
command_start + 'get_python_inc(plat_specific=True, prefix=""))'])
258+
pure_site_packages_dir, plat_site_packages_dir, inc_dir = python(
259+
'-c', commands, output=str, error=str).strip().split('\n')
260+
261+
args += ['--root=%s' % prefix,
262+
'--install-purelib=%s' % pure_site_packages_dir,
263+
'--install-platlib=%s' % plat_site_packages_dir,
264+
'--install-scripts=bin',
265+
'--install-data=""',
266+
'--install-headers=%s' % inc_dir
267+
]
247268

248269
return args
249270

lib/spack/spack/cmd/clean.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
import llnl.util.tty as tty
1111

1212
import spack.caches
13+
import spack.config
1314
import spack.cmd.test
1415
import spack.cmd.common.arguments as arguments
16+
import spack.main
1517
import spack.repo
1618
import spack.stage
17-
import spack.config
1819
from spack.paths import lib_path, var_path
1920

2021

@@ -26,7 +27,7 @@
2627
class AllClean(argparse.Action):
2728
"""Activates flags -s -d -f -m and -p simultaneously"""
2829
def __call__(self, parser, namespace, values, option_string=None):
29-
parser.parse_args(['-sdfmp'], namespace=namespace)
30+
parser.parse_args(['-sdfmpb'], namespace=namespace)
3031

3132

3233
def setup_parser(subparser):
@@ -46,15 +47,18 @@ def setup_parser(subparser):
4647
'-p', '--python-cache', action='store_true',
4748
help="remove .pyc, .pyo files and __pycache__ folders")
4849
subparser.add_argument(
49-
'-a', '--all', action=AllClean, help="equivalent to -sdfmp", nargs=0
50+
'-b', '--bootstrap', action='store_true',
51+
help="remove software needed to bootstrap Spack")
52+
subparser.add_argument(
53+
'-a', '--all', action=AllClean, help="equivalent to -sdfmpb", nargs=0
5054
)
5155
arguments.add_common_arguments(subparser, ['specs'])
5256

5357

5458
def clean(parser, args):
5559
# If nothing was set, activate the default
5660
if not any([args.specs, args.stage, args.downloads, args.failures,
57-
args.misc_cache, args.python_cache]):
61+
args.misc_cache, args.python_cache, args.bootstrap]):
5862
args.stage = True
5963

6064
# Then do the cleaning falling through the cases
@@ -96,3 +100,10 @@ def clean(parser, args):
96100
dname = os.path.join(root, d)
97101
tty.debug('Removing {0}'.format(dname))
98102
shutil.rmtree(dname)
103+
104+
if args.bootstrap:
105+
msg = 'Removing software in "{0}"'
106+
tty.msg(msg.format(spack.paths.user_bootstrap_store))
107+
with spack.store.use_store(spack.paths.user_bootstrap_store):
108+
uninstall = spack.main.SpackCommand('uninstall')
109+
uninstall('-a', '-y')

0 commit comments

Comments
 (0)