from ..exceptions import ModuleLookupError, PrerequisiteError
from .. import config
from . import _filenames
from ..tools.utility import make_exception
from ..utility import curryname, filesys, formatDocstring
import logging, os
__all__ = ['currentfile', 'import_path', 'imports_path', 'program_path']
logger = logging.getLogger(__name__)
SUBDIR = config.intermediate_subdir()
def program_path(name, currypath, is_sourcefile=False, **ignored):
'''
The search path of a program. For a module given as a source file
(``is_sourcefile``), the directory of the file comes first, then the
entries of ``currypath``, as PAKCS searches: the imports of the program
are found beside it without a CURRYPATH that names its directory. For a
module given by name the path is ``currypath`` as a list. The tools
(sprite-exec, sprite-make) and curry.import_ of a path search this path
for the imports of the program, and the plan asks the steps about the
files of the program under it.
'''
currypath = list(currypath)
if is_sourcefile:
directory = os.path.dirname(os.path.abspath(name))
currypath = [directory] + [
entry for entry in currypath if os.path.abspath(entry) != directory
]
return currypath
def imports_path(currypath, is_sourcefile=False, **ignored):
'''
The search path of the imports of a module whose own search path is
``currypath`` (``program_path``), as the steps of a plan import them.
For a program given as a source file it is that path, so the imports are
found beside the program. None, the path of the interpreter, for a
module given by name: the path given with a name finds that module
alone, and its imports are searched as before.
'''
return list(currypath) if is_sourcefile else None
def import_path(interp, currypath):
'''
The search path of the imports of a program on ``currypath``
(``imports_path``): that path, then the entries of the path of ``interp``
it lacks. So the imports of a program are found beside it and on the
path given for it, and the Prelude and the other modules of the library
as before, whatever path a caller gave. None, the path of the
interpreter, without a path.
'''
if currypath is None:
return None
path = curryname.makeCurryPath(currypath)
for entry in curryname.makeCurryPath(interp.path):
if entry not in path:
path.append(entry)
return path
[docs]
@formatDocstring(config.python_package_name())
def currentfile(
plan, name, currypath, is_sourcefile=False, **ignored
):
'''
Finds the newest prerequisite along the Curry build pipeline.
The file returned is the newest among the Curry source file (suffix: .curry),
the ICurry file (suffix: .icy), the JSON file (suffix: .json or .json.z),
and the files of the backend (suffix: .py; or .cpp and .so), less the files
that a step of the plan refuses (see ``Plan.prune_stale``) and the files
made from them. The files are compared by modification time (see
``filesys.newer``), so a step that moves the change times of the inodes
(a chmod, a rename, the prefix patch of a package manager) does not make
a product stale.
Args:
plan:
The compile plan.
name:
The module, source file, or package name.
currypath:
A sequence of paths to search (i.e., CURRYPATH split on ':').
is_sourcefile:
Indicates whether to interpret the name as a source file. If True, the
name should end with extension .curry. Otherwise the name is
interpreted as a module or package name.
plan:
The compilation :class:`plan <{0}.toolchain.plan.Plan>` This indicates
which files to consider as prerequisites. If None, a default plan
Raises:
ModuleLookupError: the module was not found.
Returns:
The name of an ICurry-JSON file (suffix: .json), Curry source file (suffix:
.curry), Python file (suffix: .py), C++ source (suffix: .cpp) or object
(suffix: .so), or directory. The JSON name may have an additional .z
suffix. The JSON file is returned if it is up-to-date, otherwise, the
Curry file is returned if it exists. If neither of those applies, the
package directory name is returned, if it exists.
'''
currypath = program_path(name, currypath, is_sourcefile)
if not is_sourcefile:
# If name is a module name, then search CURRYPATH for the source file or
# (possibly zipped) JSON and set it as the name. source file and
# is_sourcefile=True. It is acceptable to use a JSON file that has no
# corresponding source file. This means a library could be installed as
# JSON only, without needing to install its source.
curryname.validateModulename(name)
# Search for the JSON file first, then ICurry, then .curry.
parts = name.split('.')
package_path, name = os.sep.join(parts[:-1]), parts[-1]
search_names = [
os.path.join(package_path, '.curry', SUBDIR, name + suffix)
for suffix in reversed(plan.suffixes[1:])
]
search_names += [os.path.join(package_path, name + '.curry')]
search_names += [os.path.join(package_path, name, '')]
files = filesys.findfiles(currypath, search_names)
try:
name = next(files)
except StopIteration:
raise make_exception(
ModuleLookupError
, 'Curry module %r not found.' % name
, hint=lambda:_fileNotFoundHint(name)
)
if os.path.isdir(name):
return name
else:
name = _filenames.curryfilename(name)
# Find the newest prerequisite.
curryfile = name
if not curryfile.endswith('.curry'):
raise ModuleLookupError('expected .curry extension in %r' % curryfile)
curryfile = os.path.abspath(curryfile)
filelist = plan.filelist(curryfile)
# Force recompile, if requested.
if config.force_recompile_cxx():
filelist = [
fn for fn in filelist
if not any(fn.endswith(suffix) for suffix in ['.cpp', '.so'])
]
# Disregard a file the toolchain no longer accepts, and everything made
# from it: a .cpp file of another format, or a .so file compiled against
# other runtime headers. The age of the runtime library does not count.
filelist = plan.prune_stale(
filelist, imports_path(currypath, is_sourcefile)
)
prereq = os.path.abspath(filesys.newest(filelist))
if not os.path.exists(prereq):
# If there is no prerequisite, then there is no Curry file or any of its
# derivatives (it could be OK to ship Curry files with no source). Report
# this as there being no source file.
assert not os.path.exists(curryfile)
raise PrerequisiteError('Curry file does not exist: %r' % curryfile)
if not (os.access(prereq, os.R_OK) and os.path.isfile(prereq)):
raise PrerequisiteError(
'prerequisite is the wrong type or is unreadable: %r' % prereq
)
logger.debug('Prerequisite for compilation of %r is %r', name, prereq)
return prereq
# Keyword defaults.
def _fileNotFoundHint(name, **kwds):
if os.path.exists(name + '.curry'):
return 'Perhaps you meant "%s.curry"?' % name