# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import re
import urllib.parse
import llnl.util.tty as tty
from llnl.util.filesystem import mkdirp
import spack.repo
import spack.stage
import spack.util.web
from spack.spec import Spec
from spack.url import UndetectableNameError, UndetectableVersionError, parse_name, parse_version
from spack.util.editor import editor
from spack.util.executable import ProcessError, which
from spack.util.naming import mod_to_class, simplify_name, valid_fully_qualified_module_name
description = "create a new package file"
section = "packaging"
level = "short"
package_template = '''\
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
# ----------------------------------------------------------------------------
# If you submit this package back to Spack as a pull request,
# please first remove this boilerplate and all FIXME comments.
#
# This is a template package file for Spack. We've put "FIXME"
# next to all the things you'll want to change. Once you've handled
# them, you can save this file and test your package like this:
#
# spack install {name}
#
# You can edit this file again by typing:
#
# spack edit {name}
#
# See the Spack documentation for more information on packaging.
# ----------------------------------------------------------------------------
from spack.package import *
class {class_name}({base_class_name}):
"""FIXME: Put a proper description of your package here."""
# FIXME: Add a proper url for your package's homepage here.
homepage = "https://www.example.com"
{url_def}
# FIXME: Add a list of GitHub accounts to
# notify when the package is updated.
# maintainers("github_user1", "github_user2")
{versions}
{dependencies}
{body_def}
'''
class BundlePackageTemplate:
"""
Provides the default values to be used for a bundle package file template.
"""
base_class_name = "BundlePackage"
dependencies = """\
# FIXME: Add dependencies if required.
# depends_on("foo")"""
url_def = " # There is no URL since there is no code to download."
body_def = " # There is no need for install() since there is no code."
def __init__(self, name, versions):
self.name = name
self.class_name = mod_to_class(name)
self.versions = versions
def write(self, pkg_path):
"""Writes the new package file."""
# Write out a template for the file
with open(pkg_path, "w") as pkg_file:
pkg_file.write(
package_template.format(
name=self.name,
class_name=self.class_name,
base_class_name=self.base_class_name,
url_def=self.url_def,
versions=self.versions,
dependencies=self.dependencies,
body_def=self.body_def,
)
)
class PackageTemplate(BundlePackageTemplate):
"""Provides the default values to be used for the package file template"""
base_class_name = "Package"
body_def = """\
def install(self, spec, prefix):
# FIXME: Unknown build system
make()
make("install")"""
url_line = ' url = "{url}"'
def __init__(self, name, url, versions):
super().__init__(name, versions)
self.url_def = self.url_line.format(url=url)
class AutotoolsPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for Autotools-based packages
that *do* come with a ``configure`` script"""
base_class_name = "AutotoolsPackage"
body_def = """\
def configure_args(self):
# FIXME: Add arguments other than --prefix
# FIXME: If not needed delete this function
args = []
return args"""
class AutoreconfPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for Autotools-based packages
that *do not* come with a ``configure`` script"""
base_class_name = "AutotoolsPackage"
dependencies = """\
depends_on("autoconf", type="build")
depends_on("automake", type="build")
depends_on("libtool", type="build")
depends_on("m4", type="build")
# FIXME: Add additional dependencies if required.
# depends_on("foo")"""
body_def = """\
def autoreconf(self, spec, prefix):
# FIXME: Modify the autoreconf method as necessary
autoreconf("--install", "--verbose", "--force")
def configure_args(self):
# FIXME: Add arguments other than --prefix
# FIXME: If not needed delete this function
args = []
return args"""
class CMakePackageTemplate(PackageTemplate):
"""Provides appropriate overrides for CMake-based packages"""
base_class_name = "CMakePackage"
body_def = """\
def cmake_args(self):
# FIXME: Add arguments other than
# FIXME: CMAKE_INSTALL_PREFIX and CMAKE_BUILD_TYPE
# FIXME: If not needed delete this function
args = []
return args"""
class LuaPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for LuaRocks-based packages"""
base_class_name = "LuaPackage"
body_def = """\
def luarocks_args(self):
# FIXME: Add arguments to `luarocks make` other than rockspec path
# FIXME: If not needed delete this function
args = []
return args"""
def __init__(self, name, url, *args, **kwargs):
# If the user provided `--name lua-lpeg`, don't rename it lua-lua-lpeg
if not name.startswith("lua-"):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to lua-{0}".format(name))
name = "lua-{0}".format(name)
super().__init__(name, url, *args, **kwargs)
class MesonPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for meson-based packages"""
base_class_name = "MesonPackage"
body_def = """\
def meson_args(self):
# FIXME: If not needed delete this function
args = []
return args"""
class QMakePackageTemplate(PackageTemplate):
"""Provides appropriate overrides for QMake-based packages"""
base_class_name = "QMakePackage"
body_def = """\
def qmake_args(self):
# FIXME: If not needed delete this function
args = []
return args"""
class MavenPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for Maven-based packages"""
base_class_name = "MavenPackage"
body_def = """\
def build(self, spec, prefix):
# FIXME: If not needed delete this function
pass"""
class SconsPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for SCons-based packages"""
base_class_name = "SConsPackage"
body_def = """\
def build_args(self, spec, prefix):
# FIXME: Add arguments to pass to build.
# FIXME: If not needed delete this function
args = []
return args"""
class WafPackageTemplate(PackageTemplate):
"""Provides appropriate override for Waf-based packages"""
base_class_name = "WafPackage"
body_def = """\
# FIXME: Override configure_args(), build_args(),
# or install_args() if necessary."""
class BazelPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for Bazel-based packages"""
dependencies = """\
# FIXME: Add additional dependencies if required.
depends_on("bazel", type="build")"""
body_def = """\
def install(self, spec, prefix):
# FIXME: Add logic to build and install here.
bazel()"""
class RacketPackageTemplate(PackageTemplate):
"""Provides approriate overrides for Racket extensions"""
base_class_name = "RacketPackage"
url_line = """\
# FIXME: set the proper location from which to fetch your package
git = "git@github.com:example/example.git"
"""
dependencies = """\
# FIXME: Add dependencies if required. Only add the racket dependency
# if you need specific versions. A generic racket dependency is
# added implicity by the RacketPackage class.
# depends_on("racket@8.3:", type=("build", "run"))"""
body_def = """\
# FIXME: specify the name of the package,
# as it should appear to ``raco pkg install``
name = "{0}"
# FIXME: set to true if published on pkgs.racket-lang.org
# pkgs = False
# FIXME: specify path to the root directory of the
# package, if not the base directory
# subdirectory = None
"""
def __init__(self, name, url, *args, **kwargs):
# If the user provided `--name rkt-scribble`, don't rename it rkt-rkt-scribble
if not name.startswith("rkt-"):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to rkt-{0}".format(name))
name = "rkt-{0}".format(name)
self.body_def = self.body_def.format(name[4:])
super().__init__(name, url, *args, **kwargs)
class PythonPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for python extensions"""
base_class_name = "PythonPackage"
dependencies = """\
# FIXME: Only add the python/pip/wheel dependencies if you need specific versions
# or need to change the dependency type. Generic python/pip/wheel dependencies are
# added implicity by the PythonPackage base class.
# depends_on("python@2.X:2.Y,3.Z:", type=("build", "run"))
# depends_on("py-pip@X.Y:", type="build")
# depends_on("py-wheel@X.Y:", type="build")
# FIXME: Add a build backend, usually defined in pyproject.toml. If no such file
# exists, use setuptools.
# depends_on("py-setuptools", type="build")
# depends_on("py-flit-core", type="build")
# depends_on("py-poetry-core", type="build")
# FIXME: Add additional dependencies if required.
# depends_on("py-foo", type=("build", "run"))"""
body_def = """\
def global_options(self, spec, prefix):
# FIXME: Add options to pass to setup.py
# FIXME: If not needed, delete this function
options = []
return options
def install_options(self, spec, prefix):
# FIXME: Add options to pass to setup.py install
# FIXME: If not needed, delete this function
options = []
return options"""
def __init__(self, name, url, *args, **kwargs):
# If the user provided `--name py-numpy`, don't rename it py-py-numpy
if not name.startswith("py-"):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to py-{0}".format(name))
name = "py-{0}".format(name)
# Simple PyPI URLs:
# https://<hostname>/packages/<type>/<first character of project>/<project>/<download file>
# e.g. https://pypi.io/packages/source/n/numpy/numpy-1.19.4.zip
# e.g. https://www.pypi.io/packages/source/n/numpy/numpy-1.19.4.zip
# e.g. https://pypi.org/packages/source/n/numpy/numpy-1.19.4.zip
# e.g. https://pypi.python.org/packages/source/n/numpy/numpy-1.19.4.zip
# e.g. https://files.pythonhosted.org/packages/source/n/numpy/numpy-1.19.4.zip
# PyPI URLs containing hash:
# https://<hostname>/packages/<two character hash>/<two character hash>/<longer hash>/<download file>
# e.g. https://pypi.io/packages/c5/63/a48648ebc57711348420670bb074998f79828291f68aebfff1642be212ec/numpy-1.19.4.zip
# e.g. https://files.pythonhosted.org/packages/c5/63/a48648ebc57711348420670bb074998f79828291f68aebfff1642be212ec/numpy-1.19.4.zip
# e.g. https://files.pythonhosted.org/packages/c5/63/a48648ebc57711348420670bb074998f79828291f68aebfff1642be212ec/numpy-1.19.4.zip#sha256=141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512
# PyPI URLs for wheels:
# https://pypi.io/packages/py3/a/azureml_core/azureml_core-1.11.0-py3-none-any.whl
# https://pypi.io/packages/py3/d/dotnetcore2/dotnetcore2-2.1.14-py3-none-macosx_10_9_x86_64.whl
# https://pypi.io/packages/py3/d/dotnetcore2/dotnetcore2-2.1.14-py3-none-manylinux1_x86_64.whl
# https://files.pythonhosted.org/packages/cp35.cp36.cp37.cp38.cp39/s/shiboken2/shiboken2-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl
# https://files.pythonhosted.org/packages/f4/99/ad2ef1aeeb395ee2319bb981ea08dbbae878d30dd28ebf27e401430ae77a/azureml_core-1.36.0.post2-py3-none-any.whl#sha256=60bcad10b4380d78a8280deb7365de2c2cd66527aacdcb4a173f613876cbe739
match = re.search(r"(?:pypi|pythonhosted)[^/]+/packages" + "/([^/#]+)" * 4, url)
if match:
# PyPI URLs for wheels are too complicated, ignore them for now
# https://www.python.org/dev/peps/pep-0427/#file-name-convention
if not match.group(4).endswith(".whl"):
if len(match.group(2)) == 1:
# Simple PyPI URL
url = "/".join(match.group(3, 4))
else:
# PyPI URL containing hash
# Project name doesn't necessarily match download name, but it
# usually does, so this is the best we can do
project = parse_name(url)
url = "/".join([project, match.group(4)])
self.url_line = ' pypi = "{url}"'
else:
# Add a reminder about spack preferring PyPI URLs
self.url_line = (
"""
# FIXME: ensure the package is not available through PyPI. If it is,
# re-run `spack create --force` with the PyPI URL.
"""
+ self.url_line
)
super().__init__(name, url, *args, **kwargs)
class RPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for R extensions"""
base_class_name = "RPackage"
dependencies = """\
# FIXME: Add dependencies if required.
# depends_on("r-foo", type=("build", "run"))"""
body_def = """\
def configure_args(self):
# FIXME: Add arguments to pass to install via --configure-args
# FIXME: If not needed delete this function
args = []
return args"""
def __init__(self, name, url, *args, **kwargs):
# If the user provided `--name r-rcpp`, don't rename it r-r-rcpp
if not name.startswith("r-"):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to r-{0}".format(name))
name = "r-{0}".format(name)
r_name = parse_name(url)
cran = re.search(r"(?:r-project|rstudio)[^/]+/src" + "/([^/]+)" * 2, url)
if cran:
url = r_name
self.url_line = ' cran = "{url}"'
bioc = re.search(r"(?:bioconductor)[^/]+/packages" + "/([^/]+)" * 5, url)
if bioc:
self.url_line = ' url = "{0}"\n' ' bioc = "{1}"'.format(url, r_name)
super().__init__(name, url, *args, **kwargs)
class PerlmakePackageTemplate(PackageTemplate):
"""Provides appropriate overrides for Perl extensions
that come with a Makefile.PL"""
base_class_name = "PerlPackage"
dependencies = """\
# FIXME: Add dependencies if required:
# depends_on("perl-foo", type=("build", "run"))"""
body_def = """\
def configure_args(self):
# FIXME: Add non-standard arguments
# FIXME: If not needed delete this function
args = []
return args"""
def __init__(self, name, *args, **kwargs):
# If the user provided `--name perl-cpp`, don't rename it perl-perl-cpp
if not name.startswith("perl-"):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to perl-{0}".format(name))
name = "perl-{0}".format(name)
super().__init__(name, *args, **kwargs)
class PerlbuildPackageTemplate(PerlmakePackageTemplate):
"""Provides appropriate overrides for Perl extensions
that come with a Build.PL instead of a Makefile.PL"""
dependencies = """\
depends_on("perl-module-build", type="build")
# FIXME: Add additional dependencies if required:
# depends_on("perl-foo", type=("build", "run"))"""
class OctavePackageTemplate(PackageTemplate):
"""Provides appropriate overrides for octave packages"""
base_class_name = "OctavePackage"
dependencies = """\
extends("octave")
# FIXME: Add additional dependencies if required.
# depends_on("octave-foo", type=("build", "run"))"""
def __init__(self, name, *args, **kwargs):
# If the user provided `--name octave-splines`, don't rename it
# octave-octave-splines
if not name.startswith("octave-"):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to octave-{0}".format(name))
name = "octave-{0}".format(name)
super().__init__(name, *args, **kwargs)
class RubyPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for Ruby packages"""
base_class_name = "RubyPackage"
dependencies = """\
# FIXME: Add dependencies if required. Only add the ruby dependency
# if you need specific versions. A generic ruby dependency is
# added implicity by the RubyPackage class.
# depends_on("ruby@X.Y.Z:", type=("build", "run"))
# depends_on("ruby-foo", type=("build", "run"))"""
body_def = """\
def build(self, spec, prefix):
# FIXME: If not needed delete this function
pass"""
def __init__(self, name, *args, **kwargs):
# If the user provided `--name ruby-numpy`, don't rename it
# ruby-ruby-numpy
if not name.startswith("ruby-"):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to ruby-{0}".format(name))
name = "ruby-{0}".format(name)
super().__init__(name, *args, **kwargs)
class MakefilePackageTemplate(PackageTemplate):
"""Provides appropriate overrides for Makefile packages"""
base_class_name = "MakefilePackage"
body_def = """\
def edit(self, spec, prefix):
# FIXME: Edit the Makefile if necessary
# FIXME: If not needed delete this function
# makefile = FileFilter("Makefile")
# makefile.filter("CC = .*", "CC = cc")
pass"""
class IntelPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for licensed Intel software"""
base_class_name = "IntelPackage"
body_def = """\
# FIXME: Override `setup_environment` if necessary."""
class SIPPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for SIP packages."""
base_class_name = "SIPPackage"
body_def = """\
def configure_args(self, spec, prefix):
# FIXME: Add arguments other than --bindir and --destdir
# FIXME: If not needed delete this function
args = []
return args"""
def __init__(self, name, *args, **kwargs):
# If the user provided `--name py-pyqt4`, don't rename it py-py-pyqt4
if not name.startswith("py-"):
# Make it more obvious that we are renaming the package
tty.msg("Changing package name from {0} to py-{0}".format(name))
name = "py-{0}".format(name)
super().__init__(name, *args, **kwargs)
templates = {
"autotools": AutotoolsPackageTemplate,
"autoreconf": AutoreconfPackageTemplate,
"cmake": CMakePackageTemplate,
"bundle": BundlePackageTemplate,
"qmake": QMakePackageTemplate,
"maven": MavenPackageTemplate,
"scons": SconsPackageTemplate,
"waf": WafPackageTemplate,
"bazel": BazelPackageTemplate,
"python": PythonPackageTemplate,
"r": RPackageTemplate,
"racket": RacketPackageTemplate,
"perlmake": PerlmakePackageTemplate,
"perlbuild": PerlbuildPackageTemplate,
"octave": OctavePackageTemplate,
"ruby": RubyPackageTemplate,
"makefile": MakefilePackageTemplate,
"intel": IntelPackageTemplate,
"meson": MesonPackageTemplate,
"lua": LuaPackageTemplate,
"sip": SIPPackageTemplate,
"generic": PackageTemplate,
}
def setup_parser(subparser):
subparser.add_argument("url", nargs="?", help="url of package archive")
subparser.add_argument(
"--keep-stage",
action="store_true",
help="don't clean up staging area when command completes",
)
subparser.add_argument("-n", "--name", help="name of the package to create")
subparser.add_argument(
"-t",
"--template",
metavar="TEMPLATE",
choices=sorted(templates.keys()),
help="build system template to use. options: %(choices)s",
)
subparser.add_argument(
"-r", "--repo", help="path to a repository where the package should be created"
)
subparser.add_argument(
"-N",
"--namespace",
help="specify a namespace for the package. must be the namespace of "
"a repository registered with Spack",
)
subparser.add_argument(
"-f",
"--force",
action="store_true",
help="overwrite any existing package file with the same name",
)
subparser.add_argument(
"--skip-editor",
action="store_true",
help="skip the edit session for the package (e.g., automation)",
)
subparser.add_argument(
"-b", "--batch", action="store_true", help="don't ask which versions to checksum"
)
class BuildSystemGuesser:
"""An instance of BuildSystemGuesser provides a callable object to be used
during ``spack create``. By passing this object to ``spack checksum``, we
can take a peek at the fetched tarball and discern the build system it uses
"""
def __init__(self):
"""Sets the default build system."""
self.build_system = "generic"
def __call__(self, stage, url):
"""Try to guess the type of build system used by a project based on
the contents of its archive or the URL it was downloaded from."""
if url is not None:
# Most octave extensions are hosted on Octave-Forge:
# https://octave.sourceforge.net/index.html
# They all have the same base URL.
if "downloads.sourceforge.net/octave/" in url:
self.build_system = "octave"
return
if url.endswith(".gem"):
self.build_system = "ruby"
return
if url.endswith(".whl") or ".whl#" in url:
self.build_system = "python"
return
if url.endswith(".rock"):
self.build_system = "lua"
return
# A list of clues that give us an idea of the build system a package
# uses. If the regular expression matches a file contained in the
# archive, the corresponding build system is assumed.
# NOTE: Order is important here. If a package supports multiple
# build systems, we choose the first match in this list.
clues = [
(r"/CMakeLists\.txt$", "cmake"),
(r"/NAMESPACE$", "r"),
(r"/configure$", "autotools"),
(r"/configure\.(in|ac)$", "autoreconf"),
(r"/Makefile\.am$", "autoreconf"),
(r"/pom\.xml$", "maven"),
(r"/SConstruct$", "scons"),
(r"/waf$", "waf"),
(r"/pyproject.toml", "python"),
(r"/setup\.(py|cfg)$", "python"),
(r"/WORKSPACE$", "bazel"),
(r"/Build\.PL$", "perlbuild"),
(r"/Makefile\.PL$", "perlmake"),
(r"/.*\.gemspec$", "ruby"),
(r"/Rakefile$", "ruby"),
(r"/setup\.rb$", "ruby"),
(r"/.*\.pro$", "qmake"),
(r"/.*\.rockspec$", "lua"),
(r"/(GNU)?[Mm]akefile$", "makefile"),
(r"/DESCRIPTION$", "octave"),
(r"/meson\.build$", "meson"),
(r"/configure\.py$", "sip"),
]
# Peek inside the compressed file.
if stage.archive_file.endswith(".zip") or ".zip#" in stage.archive_file:
try:
unzip = which("unzip")
output = unzip("-lq", stage.archive_file, output=str)
except ProcessError:
output = ""
else:
try:
tar = which("tar")
output = tar("--exclude=*/*/*", "-tf", stage.archive_file, output=str)
except ProcessError:
output = ""
lines = output.splitlines()
# Determine the build system based on the files contained
# in the archive.
for pattern, bs in clues:
if any(re.search(pattern, line) for line in lines):
self.build_system = bs
break
def get_name(name, url):
"""Get the name of the package based on the supplied arguments.
If a name was provided, always use that. Otherwise, if a URL was
provided, extract the name from that. Otherwise, use a default.
Args:
name (str): explicit ``--name`` argument given to ``spack create``
url (str): ``url`` argument given to ``spack create``
Returns:
str: The name of the package
"""
# Default package name
result = "example"
if name is not None:
# Use a user-supplied name if one is present
result = name
if len(name.strip()) > 0:
tty.msg("Using specified package name: '{0}'".format(result))
else:
tty.die("A package name must be provided when using the option.")
elif url is not None:
# Try to guess the package name based on the URL
try:
result = parse_name(url)
if result != url:
desc = "URL"
else:
desc = "package name"
tty.msg("This looks like a {0} for {1}".format(desc, result))
except UndetectableNameError:
tty.die(
"Couldn't guess a name for this package.",
" Please report this bug. In the meantime, try running:",
" `spack create --name <name> <url>`",
)
result = simplify_name(result)
if not valid_fully_qualified_module_name(result):
tty.die("Package name can only contain a-z, 0-9, and '-'")
return result
def get_url(url):
"""Get the URL to use.
Use a default URL if none is provided.
Args:
url (str): ``url`` argument to ``spack create``
Returns:
str: The URL of the package
"""
# Use the user-supplied URL or a default URL if none is present.
return url or "https://www.example.com/example-1.2.3.tar.gz"
def get_versions(args, name):
"""Returns a list of versions and hashes for a package.
Also returns a BuildSystemGuesser object.
Returns default values if no URL is provided.
Args:
args (argparse.Namespace): The arguments given to ``spack create``
name (str): The name of the package
Returns:
tuple: versions and hashes, and a BuildSystemGuesser object
"""
# Default version with hash
hashed_versions = """\
# FIXME: Add proper versions and checksums here.
# version("1.2.3", md5="0123456789abcdef0123456789abcdef")"""
# Default version without hash
unhashed_versions = """\
# FIXME: Add proper versions here.
# version("1.2.4")"""
# Default guesser
guesser = BuildSystemGuesser()
valid_url = True
try:
parsed = urllib.parse.urlparse(args.url)
if not parsed.scheme or parsed.scheme == "file":
valid_url = False # No point in spidering these
except (ValueError, TypeError):
valid_url = False
if args.url is not None and args.template != "bundle" and valid_url:
# Find available versions
try:
url_dict = spack.util.web.find_versions_of_archive(args.url)
except UndetectableVersionError:
# Use fake versions
tty.warn("Couldn't detect version in: {0}".format(args.url))
return hashed_versions, guesser
if not url_dict:
# If no versions were found, revert to what the user provided
version = parse_version(args.url)
url_dict = {version: args.url}
versions = spack.stage.get_checksums_for_versions(
url_dict,
name,
first_stage_function=guesser,
keep_stage=args.keep_stage,
batch=(args.batch or len(url_dict) == 1),
)
else:
versions = unhashed_versions
return versions, guesser
def get_build_system(template, url, guesser):
"""Determine the build system template.
If a template is specified, always use that. Otherwise, if a URL
is provided, download the tarball and peek inside to guess what
build system it uses. Otherwise, use a generic template by default.
Args:
template (str): ``--template`` argument given to ``spack create``
url (str): ``url`` argument given to ``spack create``
args (argparse.Namespace): The arguments given to ``spack create``
guesser (BuildSystemGuesser): The first_stage_function given to
``spack checksum`` which records the build system it detects
Returns:
str: The name of the build system template to use
"""
# Default template
selected_template = "generic"
if template is not None:
selected_template = template
# Use a user-supplied template if one is present
tty.msg("Using specified package template: '{0}'".format(selected_template))
elif url is not None:
# Use whatever build system the guesser detected
selected_template = guesser.build_system
if selected_template == "generic":
tty.warn("Unable to detect a build system. " "Using a generic package template.")
else:
msg = "This package looks like it uses the {0} build system"
tty.msg(msg.format(selected_template))
return selected_template
def get_repository(args, name):
"""Returns a Repo object that will allow us to determine the path where
the new package file should be created.
Args:
args (argparse.Namespace): The arguments given to ``spack create``
name (str): The name of the package to create
Returns:
spack.repo.Repo: A Repo object capable of determining the path to the
package file
"""
spec = Spec(name)
# Figure out namespace for spec
if spec.namespace and args.namespace and spec.namespace != args.namespace:
tty.die("Namespaces '{0}' and '{1}' do not match.".format(spec.namespace, args.namespace))
if not spec.namespace and args.namespace:
spec.namespace = args.namespace
# Figure out where the new package should live
repo_path = args.repo
if repo_path is not None:
repo = spack.repo.Repo(repo_path)
if spec.namespace and spec.namespace != repo.namespace:
tty.die(
"Can't create package with namespace {0} in repo with "
"namespace {1}".format(spec.namespace, repo.namespace)
)
else:
if spec.namespace:
repo = spack.repo.path.get_repo(spec.namespace, None)
if not repo:
tty.die("Unknown namespace: '{0}'".format(spec.namespace))
else:
repo = spack.repo.path.first_repo()
# Set the namespace on the spec if it's not there already
if not spec.namespace:
spec.namespace = repo.namespace
return repo
def create(parser, args):
# Gather information about the package to be created
name = get_name(args.name, args.url)
url = get_url(args.url)
versions, guesser = get_versions(args, name)
build_system = get_build_system(args.template, url, guesser)
# Create the package template object
constr_args = {"name": name, "versions": versions}
package_class = templates[build_system]
if package_class != BundlePackageTemplate:
constr_args["url"] = url
package = package_class(**constr_args)
tty.msg("Created template for {0} package".format(package.name))
# Create a directory for the new package
repo = get_repository(args, name)
pkg_path = repo.filename_for_package_name(package.name)
if os.path.exists(pkg_path) and not args.force:
tty.die(
"{0} already exists.".format(pkg_path),
" Try running `spack create --force` to overwrite it.",
)
else:
mkdirp(os.path.dirname(pkg_path))
# Write the new package file
package.write(pkg_path)
tty.msg("Created package file: {0}".format(pkg_path))
# Optionally open up the new package file in your $EDITOR
if not args.skip_editor:
editor(pkg_path)