1# Copyright 2017 The Meson development team 2 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6 7# http://www.apache.org/licenses/LICENSE-2.0 8 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Code that creates simple startup projects.""" 16 17from pathlib import Path 18from enum import Enum 19import subprocess 20import shutil 21import sys 22import os 23import re 24from glob import glob 25from mesonbuild import mesonlib 26from mesonbuild.environment import detect_ninja 27from mesonbuild.templates.samplefactory import sameple_generator 28import typing as T 29 30if T.TYPE_CHECKING: 31 import argparse 32 33''' 34we currently have one meson template at this time. 35''' 36from mesonbuild.templates.mesontemplates import create_meson_build 37 38FORTRAN_SUFFIXES = {'.f', '.for', '.F', '.f90', '.F90'} 39LANG_SUFFIXES = {'.c', '.cc', '.cpp', '.cs', '.cu', '.d', '.m', '.mm', '.rs', '.java'} | FORTRAN_SUFFIXES 40LANG_SUPPORTED = {'c', 'cpp', 'cs', 'cuda', 'd', 'fortran', 'java', 'rust', 'objc', 'objcpp'} 41 42DEFAULT_PROJECT = 'executable' 43DEFAULT_VERSION = '0.1' 44class DEFAULT_TYPES(Enum): 45 EXE = 'executable' 46 LIB = 'library' 47 48INFO_MESSAGE = '''Sample project created. To build it run the 49following commands: 50 51meson setup builddir 52meson compile -C builddir 53''' 54 55 56def create_sample(options: 'argparse.Namespace') -> None: 57 ''' 58 Based on what arguments are passed we check for a match in language 59 then check for project type and create new Meson samples project. 60 ''' 61 sample_gen = sameple_generator(options) 62 if options.type == DEFAULT_TYPES['EXE'].value: 63 sample_gen.create_executable() 64 elif options.type == DEFAULT_TYPES['LIB'].value: 65 sample_gen.create_library() 66 else: 67 raise RuntimeError('Unreachable code') 68 print(INFO_MESSAGE) 69 70def autodetect_options(options: 'argparse.Namespace', sample: bool = False) -> None: 71 ''' 72 Here we autodetect options for args not passed in so don't have to 73 think about it. 74 ''' 75 if not options.name: 76 options.name = Path().resolve().stem 77 if not re.match('[a-zA-Z_][a-zA-Z0-9]*', options.name) and sample: 78 raise SystemExit(f'Name of current directory "{options.name}" is not usable as a sample project name.\n' 79 'Specify a project name with --name.') 80 print(f'Using "{options.name}" (name of current directory) as project name.') 81 if not options.executable: 82 options.executable = options.name 83 print(f'Using "{options.executable}" (project name) as name of executable to build.') 84 if sample: 85 # The rest of the autodetection is not applicable to generating sample projects. 86 return 87 if not options.srcfiles: 88 srcfiles = [] 89 for f in (f for f in Path().iterdir() if f.is_file()): 90 if f.suffix in LANG_SUFFIXES: 91 srcfiles.append(f) 92 if not srcfiles: 93 raise SystemExit('No recognizable source files found.\n' 94 'Run meson init in an empty directory to create a sample project.') 95 options.srcfiles = srcfiles 96 print("Detected source files: " + ' '.join(map(str, srcfiles))) 97 options.srcfiles = [Path(f) for f in options.srcfiles] 98 if not options.language: 99 for f in options.srcfiles: 100 if f.suffix == '.c': 101 options.language = 'c' 102 break 103 if f.suffix in ('.cc', '.cpp'): 104 options.language = 'cpp' 105 break 106 if f.suffix == '.cs': 107 options.language = 'cs' 108 break 109 if f.suffix == '.cu': 110 options.language = 'cuda' 111 break 112 if f.suffix == '.d': 113 options.language = 'd' 114 break 115 if f.suffix in FORTRAN_SUFFIXES: 116 options.language = 'fortran' 117 break 118 if f.suffix == '.rs': 119 options.language = 'rust' 120 break 121 if f.suffix == '.m': 122 options.language = 'objc' 123 break 124 if f.suffix == '.mm': 125 options.language = 'objcpp' 126 break 127 if f.suffix == '.java': 128 options.language = 'java' 129 break 130 if not options.language: 131 raise SystemExit("Can't autodetect language, please specify it with -l.") 132 print("Detected language: " + options.language) 133 134def add_arguments(parser: 'argparse.ArgumentParser') -> None: 135 ''' 136 Here we add args for that the user can passed when making a new 137 Meson project. 138 ''' 139 parser.add_argument("srcfiles", metavar="sourcefile", nargs="*", help="source files. default: all recognized files in current directory") 140 parser.add_argument('-C', dest='wd', action=mesonlib.RealPathAction, 141 help='directory to cd into before running') 142 parser.add_argument("-n", "--name", help="project name. default: name of current directory") 143 parser.add_argument("-e", "--executable", help="executable name. default: project name") 144 parser.add_argument("-d", "--deps", help="dependencies, comma-separated") 145 parser.add_argument("-l", "--language", choices=sorted(LANG_SUPPORTED), help="project language. default: autodetected based on source files") 146 parser.add_argument("-b", "--build", action='store_true', help="build after generation") 147 parser.add_argument("--builddir", default='build', help="directory for build") 148 parser.add_argument("-f", "--force", action="store_true", help="force overwrite of existing files and directories.") 149 parser.add_argument('--type', default=DEFAULT_PROJECT, choices=('executable', 'library'), help=f"project type. default: {DEFAULT_PROJECT} based project") 150 parser.add_argument('--version', default=DEFAULT_VERSION, help=f"project version. default: {DEFAULT_VERSION}") 151 152def run(options: 'argparse.Namespace') -> int: 153 ''' 154 Here we generate the new Meson sample project. 155 ''' 156 if not Path(options.wd).exists(): 157 sys.exit('Project source root directory not found. Run this command in source directory root.') 158 os.chdir(options.wd) 159 160 if not glob('*'): 161 autodetect_options(options, sample=True) 162 if not options.language: 163 print('Defaulting to generating a C language project.') 164 options.language = 'c' 165 create_sample(options) 166 else: 167 autodetect_options(options) 168 if Path('meson.build').is_file() and not options.force: 169 raise SystemExit('meson.build already exists. Use --force to overwrite.') 170 create_meson_build(options) 171 if options.build: 172 if Path(options.builddir).is_dir() and options.force: 173 print('Build directory already exists, deleting it.') 174 shutil.rmtree(options.builddir) 175 print('Building...') 176 cmd = mesonlib.get_meson_command() + [options.builddir] 177 ret = subprocess.run(cmd) 178 if ret.returncode: 179 raise SystemExit 180 cmd = detect_ninja() + ['-C', options.builddir] 181 ret = subprocess.run(cmd) 182 if ret.returncode: 183 raise SystemExit 184 return 0 185