summaryrefslogblamecommitdiff
path: root/lib/spack/spack/cmd/create.py
blob: 2012257239723c3fadae36ae9e08b4f740dd1bd6 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                         
                                                                         
 

                                              
         
         
                   
 
                           

                                       
                 
                  
                     
                           
                                                                                                
                                    
                                                     
                                                                                            
 
                                         


                     
 
                       
                                                                         







                                                                              


                                                                   
 
                          
 
                                         
 
                       
 
                                                                

                                                                              
                           
 
 

                                                               
 
                                                               
                                        
         
 

                                             
                                                 
 
          
 
              
 
          
   
 
 
                            


                                                                              
 
                                     
 

                                          
                          
 

                                                                             
 
                                       
                        
                                            
                                





                                             










                                                         




                                                                              
                               




                                     
                          
 
                                  

                                            
                                        

                                                    
 
 
                                                

                                                                  
 
                                        
 
                   
                             
                                                  
                                                   

                      
 
 



                                                                  
                                        

                       



                                        

                                                     
                          
 
                   

                                                          
                                                       







                                                   


                                                                 
                                    
 
                   
                         

                                                          
                                                   

                      
 
 


                                                                    
                                  









                                                                              
                                       

                                                                             
                                         
                                                    

 


                                                                 
                                    
 
                   





                                                   


                                                                 
                                    
 
                   





                                                   


                                                                 
                                    






                                                   


                                                                 
                                    
 
                   




                                                   
 
 


                                                              
                                  
 
                   



                                                     


                                                                 

                                                     
                                        
 
                   
                                    
                                                     

                  
 

                                                             

                                     


                                                                     
                                              





                                                                         
                                                         



                                                 
                








                                                                                      
                                       

                                                                             
                                         
                                                      
                                                    

 
                                             
                                                              

                                     
 
                       


                                                                                      


                                                              


                                                                                    


                                                

                                                     
                                                    
 
                   










                                                        
 
                                                   
                                                                             
                                      

                                                                            
                                        
 













                                                                                                                                                                                                                  





                                                                                                                                                                                                                                       
 
                                                                                        
                 

                                                                            
                                                   

                                            
                                                     




                                                                                  
                                                             
 
                                                    

                                                             

                             

                                                                        


                               
 
                                                    
 

                                        
                                                         

                                
 
                       
                                          
                                                   
 
                   
                             



                                                                      
 
                                                   
                                                                        
                                     

                                                                           
                                       
 

                                
                                                                                 


                        
                                                
 
                                                                                 

                
                                                                                      
 
                                                    
 
 


                                                         

                                   


                                          
                                                      
 
                   




                                                   
 
                                              
                                                                               
                                        

                                                                              
                                          
 
                                               




                                                         
 
                       
                                                 

                                                     
                                                      

 
                                             
                                                            
 
                                     
 
                       
                     

                                                     
                                                        
 
                                              

                                                                       
                                          
                                                                   
                                                                                
                                            
 
                                               

 


                                                          
                                   




                                                                       

                                                      








                                                                   
                                        

                                                                              
                                          
 
                                               

 


                                                              
                                       
 
                   


                                                   


                                               

 


                                                                    
                                    
 
                   


                                                          


                                                          
                                  
 
                   





                                                                
                                              
                                                                             
                                      

                                                                            
                                        
 
                                               

 
             





















                                            
 

 
                            
                                                                           
                           




                                                                                
                           


                           
                                         

                                                                  
                           

                                                                                       
                           

                      
                                                                             

                                             
                           




                                                                      
                           



                                                                        
                           

                                                                                         
 
 




                                                                               
 

                                            
                                     
 


                                                                           
 



                                                                

                                                          
                      

                                          
                      

                                                      
                      

                                         
                      



                                                                            

                                                                       
                 





















                                                    


                                          
                                                                                
                

                                                                     
                                
                           

                

                                                                                      
                                
                           
                                   


                                                                 
                                 
                                                               
                                      
                     
 
 
                        
                                                                   
 

                                                                    
 
         

                                                                          
 

                                    


                          
                      
 
                        
                                                    


                                                                         

                                                                             
                         
                                                        
            

                                    
                            
                 
                                     
                                                                         
                                     




                                                                          
 
                                  
 
                                                     

                                                                  
                 

 
                 



                                          
         
                                                       
 

                                   

       

                                                                    








                                                           


                                                                          
 
            
                                                                   
       
 

                               
                                                    
                                                                 
 


                                      
                         

                     

                                  

                    
                                                
                                                        
                                                            
                                   

                         
                                                                        
                                 




                                                                        
                                           


                                                                         
                                             

                                          
                                                          


                                         
                                       

                                                     

                                    



                            
                                             





                                                                       
         

                                                                         


                                                                          
 

                                                         
       
                      
                                 
 

                                    
                                                        

                                                                                    
                                                        

                                                
                                                                                             

                                                                        
                                                  
 
                            





                                                                           


                                                                          
 
            

                                                                             


                                   
                                                                              
                                                                                                  



                                             
                                                  

                             
                                         
                                                               



                                                                       
         
                          
                                                                 
                        
                                                                          
             
                                               







                                                             
                         
                                                        

                                        
                                                
                                                                

                                        
                                                      
                                           
                                              
                                
                                          




                                                                    
                                                   



                                                                    


                                         



                                                         


                                                             
# 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)