diff --git a/backends.py b/backends.py index e0a9ec405..9abf02fc5 100644 --- a/backends.py +++ b/backends.py @@ -85,6 +85,13 @@ class Backend(): os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) return dirname + # Crap hack. The above was doing the wrong thing but too many thing use it to fix + # now. Get fixed once Swift works. + def get_target_private_dir_abs_v2(self, target): + dirname = os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target)) + os.makedirs(dirname, exist_ok=True) + return dirname + def generate_unity_files(self, target, unity_src): langlist = {} abs_files = [] @@ -134,23 +141,23 @@ class Backend(): self.write_benchmark_file(datafile) datafile.close() - def has_vala(self, target): + def has_source_suffix(self, target, suffix): for s in target.get_sources(): - if s.endswith('.vala'): + if s.endswith(suffix): return True return False + def has_vala(self, target): + return self.has_source_suffix(target, '.vala') + def has_rust(self, target): - for s in target.get_sources(): - if s.endswith('.rs'): - return True - return False + return self.has_source_suffix(target, '.rs') def has_cs(self, target): - for s in target.get_sources(): - if s.endswith('.cs'): - return True - return False + return self.has_source_suffix(target, '.cs') + + def has_swift(self, target): + return self.has_source_suffix(target, '.swift') def determine_linker(self, target, src): if isinstance(target, build.StaticLibrary): diff --git a/compilers.py b/compilers.py index 7907235d5..0567764e9 100644 --- a/compilers.py +++ b/compilers.py @@ -92,6 +92,11 @@ mono_buildtype_args = {'plain' : [], 'debugoptimized': ['-debug', '-optimize+'], 'release' : ['-optimize+']} +swift_buildtype_args = {'plain' : [], + 'debug' : ['-g'], + 'debugoptimized': ['-g', '-O'], + 'release' : ['-O']} + gnu_winlibs = ['-lkernel32', '-luser32', '-lgdi32', '-lwinspool', '-lshell32', '-lole32', '-loleaut32', '-luuid', '-lcomdlg32', '-ladvapi32'] @@ -210,6 +215,9 @@ class CCompiler(Compiler): def get_dependency_gen_args(self, outtarget, outfile): return ['-MMD', '-MQ', outtarget, '-MF', outfile] + def depfile_for_object(self, objfile): + return objfile + '.' + self.get_depfile_suffix() + def get_depfile_suffix(self): return 'd' @@ -948,6 +956,99 @@ class RustCompiler(Compiler): def get_buildtype_args(self, buildtype): return rust_buildtype_args[buildtype] +class SwiftCompiler(Compiler): + def __init__(self, exelist, version): + super().__init__(exelist, version) + self.version = version + self.id = 'llvm' + self.language = 'swift' + self.is_cross = False + + def get_id(self): + return self.id + + def get_linker_exelist(self): + return self.exelist + + def name_string(self): + return ' '.join(self.exelist) + + def needs_static_linker(self): + return True + + def get_exelist(self): + return self.exelist + + def get_werror_args(self): + return ['--fatal-warnings'] + + def get_language(self): + return self.language + + def get_dependency_gen_args(self, outtarget, outfile): + return ['-emit-dependencies'] + + def depfile_for_object(self, objfile): + return os.path.splitext(objfile)[0] + '.' + self.get_depfile_suffix() + + def get_depfile_suffix(self): + return 'd' + + def get_output_args(self, target): + return ['-o', target] + + def get_linker_output_args(self, target): + return ['-o', target] + + def get_header_import_args(self, headername): + return ['-import-objc-header', headername] + + def get_warn_args(self, level): + return [] + + def get_buildtype_args(self, buildtype): + return swift_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return [] + + def get_std_exe_link_args(self): + return ['-emit-executable'] + + def get_module_args(self, modname): + return ['-module-name', modname] + + def get_mod_gen_args(self): + return ['-emit-module'] + + def build_rpath_args(self, *args): + return [] # FIXME + + def get_include_args(self, dirname): + return ['-I' + dirname] + + def get_compile_only_args(self): + return ['-c'] + + def sanity_check(self, work_dir): + src = 'swifttest.swift' + source_name = os.path.join(work_dir, src) + output_name = os.path.join(work_dir, 'swifttest') + ofile = open(source_name, 'w') + ofile.write('''1 + 2 +''') + ofile.close() + pc = subprocess.Popen(self.exelist + ['-emit-executable', '-o', output_name, src], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Swift compiler %s can not compile programs.' % self.name_string()) + if subprocess.call(output_name) != 0: + raise EnvironmentException('Executables created by Swift compiler %s are not runnable.' % self.name_string()) + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + return suffix in ('swift') + class VisualStudioCCompiler(CCompiler): std_warn_args = ['/W3'] std_opt_args= ['/O2'] @@ -1476,6 +1577,9 @@ end program prog def get_module_outdir_args(self, path): return ['-J' + path] + def depfile_for_object(self, objfile): + return objfile + '.' + self.get_depfile_suffix() + def get_depfile_suffix(self): return 'd' diff --git a/dirchanger.py b/dirchanger.py new file mode 100755 index 000000000..4cc863adb --- /dev/null +++ b/dirchanger.py @@ -0,0 +1,24 @@ +# Copyright 2015 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +'''CD into dir given as first argument and execute +the command given in the rest of the arguments.''' + +import os, subprocess, sys + +dirname = sys.argv[1] +command = sys.argv[2:] + +os.chdir(dirname) +sys.exit(subprocess.call(command)) diff --git a/environment.py b/environment.py index c92eb87f7..308a21ce6 100644 --- a/environment.py +++ b/environment.py @@ -19,6 +19,10 @@ import configparser build_filename = 'meson.build' +class EnvironmentException(coredata.MesonException): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + def find_coverage_tools(): gcovr_exe = 'gcovr' lcov_exe = 'lcov' @@ -457,6 +461,23 @@ class Environment(): return RustCompiler(exelist, version) raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + def detect_swift_compiler(self): + exelist = ['swiftc'] + try: + p = subprocess.Popen(exelist + ['-v'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute Swift compiler "%s"' % ' '.join(exelist)) + (_, err) = p.communicate() + err = err.decode() + vmatch = re.search(Environment.version_regex, err) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if 'Swift' in err: + return SwiftCompiler(exelist, version) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + def detect_static_linker(self, compiler): if compiler.is_cross: linker = self.cross_info.config['binaries']['ar'] diff --git a/interpreter.py b/interpreter.py index a11f75373..47ce716a8 100644 --- a/interpreter.py +++ b/interpreter.py @@ -1476,6 +1476,11 @@ class Interpreter(): comp = self.environment.detect_fortran_compiler(False) if need_cross_compiler: cross_comp = self.environment.detect_fortran_compiler(True) + elif lang == 'swift': + comp = self.environment.detect_swift_compiler() + if need_cross_compiler: + raise InterpreterException('Cross compilation with Swift is not working yet.') + #cross_comp = self.environment.detect_fortran_compiler(True) else: raise InvalidCode('Tried to use unknown language "%s".' % lang) comp.sanity_check(self.environment.get_scratch_dir()) diff --git a/ninjabackend.py b/ninjabackend.py index d83707407..5c3a9e56c 100644 --- a/ninjabackend.py +++ b/ninjabackend.py @@ -195,6 +195,9 @@ class NinjaBackend(backends.Backend): return if 'vala' in self.environment.coredata.compilers.keys() and self.has_vala(target): gen_src_deps += self.generate_vala_compile(target, outfile) + if 'swift' in self.environment.coredata.compilers.keys() and self.has_swift(target): + self.generate_swift_target(target, outfile) + return self.scan_fortran_module_outputs(target) # The following deals with C/C++ compilation. (gen_src, gen_other_deps) = self.process_dep_gens(outfile, target) @@ -854,6 +857,134 @@ class NinjaBackend(backends.Backend): element.write(outfile) self.check_outputs(element) + def swift_module_file_name(self, target): + return os.path.join(self.get_target_private_dir(target), + self.target_swift_modulename(target) + '.swiftmodule') + + def target_swift_modulename(self, target): + return target.name + + def is_swift_target(self, target): + for s in target.sources: + if s.endswith('swift'): + return True + return False + + def determine_swift_dep_modules(self, target): + result = [] + for l in target.link_targets: + if self.is_swift_target(l): + result.append(self.swift_module_file_name(l)) + return result + + def determine_swift_dep_dirs(self, target): + result = [] + for l in target.link_targets: + result.append(self.get_target_private_dir_abs_v2(l)) + return result + + def get_swift_link_deps(self, target): + result = [] + for l in target.link_targets: + result.append(self.get_target_filename(l)) + return result + + def split_swift_generated_sources(self, target): + all_srcs = [] + for genlist in target.get_generated_sources(): + if isinstance(genlist, build.CustomTarget): + for ifile in genlist.get_filename(): + rel = os.path.join(self.get_target_dir(genlist), ifile) + all_srcs.append(rel) + else: + for ifile in genlist.get_outfilelist(): + rel = os.path.join(self.get_target_private_dir(target), ifile) + all_srcs.append(rel) + srcs = [] + others = [] + for i in all_srcs: + if i.endswith('.swift'): + srcs.append(i) + else: + others.append(i) + return (srcs, others) + + def generate_swift_target(self, target, outfile): + module_name = self.target_swift_modulename(target) + swiftc = self.environment.coredata.compilers['swift'] + abssrc = [] + abs_headers = [] + header_imports = [] + for i in target.get_sources(): + if swiftc.can_compile(i): + relsrc = i.rel_to_builddir(self.build_to_src) + abss = os.path.normpath(os.path.join(self.environment.get_build_dir(), relsrc)) + abssrc.append(abss) + elif self.environment.is_header(i): + relh = i.rel_to_builddir(self.build_to_src) + absh = os.path.normpath(os.path.join(self.environment.get_build_dir(), relh)) + abs_headers.append(absh) + header_imports += swiftc.get_header_import_args(absh) + else: + raise InvalidArguments('Swift target %s contains a non-swift source file.' % target.get_basename()) + os.makedirs(os.path.join(self.get_target_private_dir_abs(target)), exist_ok=True) + compile_args = swiftc.get_compile_only_args() + compile_args += swiftc.get_module_args(module_name) + link_args = swiftc.get_output_args(os.path.join(self.environment.get_build_dir(), self.get_target_filename(target))) + rundir = self.get_target_private_dir(target) + out_module_name = self.swift_module_file_name(target) + in_module_files = self.determine_swift_dep_modules(target) + abs_module_dirs = self.determine_swift_dep_dirs(target) + module_includes = [] + for x in abs_module_dirs: + module_includes += swiftc.get_include_args(x) + link_deps = self.get_swift_link_deps(target) + abs_link_deps = [os.path.join(self.environment.get_build_dir(), x) for x in link_deps] + (rel_generated, _) = self.split_swift_generated_sources(target) + abs_generated = [os.path.join(self.environment.get_build_dir(), x) for x in rel_generated] + # We need absolute paths because swiftc needs to be invoked in a subdir + # and this is the easiest way about it. + objects = [] # Relative to swift invocation dir + rel_objects = [] # Relative to build.ninja + for i in abssrc + abs_generated: + base = os.path.split(i)[1] + oname = os.path.splitext(base)[0] + '.o' + objects.append(oname) + rel_objects.append(os.path.join(self.get_target_private_dir(target), oname)) + + # Swiftc does not seem to be able to emit objects and module files in one go. + elem = NinjaBuildElement(rel_objects, + 'swift_COMPILER', + abssrc) + elem.add_dep(in_module_files + rel_generated) + elem.add_dep(abs_headers) + elem.add_item('ARGS', compile_args + header_imports + abs_generated + module_includes) + elem.add_item('RUNDIR', rundir) + elem.write(outfile) + self.check_outputs(elem) + elem = NinjaBuildElement(out_module_name, + 'swift_COMPILER', + abssrc) + elem.add_dep(in_module_files + rel_generated) + elem.add_item('ARGS', compile_args + abs_generated + module_includes + swiftc.get_mod_gen_args()) + elem.add_item('RUNDIR', rundir) + elem.write(outfile) + self.check_outputs(elem) + if isinstance(target, build.StaticLibrary): + elem = self.generate_link(target, outfile, self.get_target_filename(target), + rel_objects, self.build.static_linker) + elem.write(outfile) + elif isinstance(target, build.Executable): + elem = NinjaBuildElement(self.get_target_filename(target), 'swift_COMPILER', []) + elem.add_dep(rel_objects) + elem.add_dep(link_deps) + elem.add_item('ARGS', link_args + swiftc.get_std_exe_link_args() + objects + abs_link_deps) + elem.add_item('RUNDIR', rundir) + elem.write(outfile) + self.check_outputs(elem) + else: + raise MesonException('Swift supports only executable and static library targets.') + def generate_static_link_rules(self, is_cross, outfile): if self.build.has_language('java'): if not is_cross: @@ -991,6 +1122,19 @@ class NinjaBackend(backends.Backend): outfile.write(depstyle) outfile.write('\n') + def generate_swift_compile_rules(self, compiler, outfile): + rule = 'rule %s_COMPILER\n' % compiler.get_language() + full_exe = [sys.executable, + os.path.join(self.environment.get_script_dir(), 'dirchanger.py'), + '$RUNDIR'] + compiler.get_exelist() + invoc = ' '.join([ninja_quote(i) for i in full_exe]) + command = ' command = %s $ARGS $in\n' % invoc + description = ' description = Compiling Swift source $in.\n' + outfile.write(rule) + outfile.write(command) + outfile.write(description) + outfile.write('\n') + def generate_fortran_dep_hack(self, outfile): if mesonlib.is_windows(): cmd = 'cmd /C ""' @@ -1024,6 +1168,10 @@ rule FORTRAN_DEP_HACK if not is_cross: self.generate_rust_compile_rules(compiler, outfile) return + if langname == 'swift': + if not is_cross: + self.generate_swift_compile_rules(compiler, outfile) + return if langname == 'fortran': self.generate_fortran_dep_hack(outfile) if is_cross: @@ -1289,7 +1437,7 @@ rule FORTRAN_DEP_HACK obj_basename = src_filename.replace('/', '_').replace('\\', '_') rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename) rel_obj += '.' + self.environment.get_object_suffix() - dep_file = rel_obj + '.' + compiler.get_depfile_suffix() + dep_file = compiler.depfile_for_object(rel_obj) if self.environment.coredata.get_builtin_option('use_pch'): pchlist = target.get_pch(compiler.language) else: diff --git a/run_tests.py b/run_tests.py index e84d6100a..f07e0667e 100755 --- a/run_tests.py +++ b/run_tests.py @@ -254,6 +254,7 @@ def detect_tests_to_run(): all_tests.append(('rust', gather_tests('test cases/rust'), False if shutil.which('rustc') else True)) all_tests.append(('objective c', gather_tests('test cases/objc'), False if not mesonlib.is_windows() else True)) all_tests.append(('fortran', gather_tests('test cases/fortran'), False if shutil.which('gfortran') else True)) + all_tests.append(('swift', gather_tests('test cases/swift'), False if shutil.which('swiftc') else True)) return all_tests def run_tests(extra_args): diff --git a/test cases/swift/1 exe/meson.build b/test cases/swift/1 exe/meson.build new file mode 100644 index 000000000..f986e9af5 --- /dev/null +++ b/test cases/swift/1 exe/meson.build @@ -0,0 +1,3 @@ +project('swift exe', 'swift') + +test('swifttest', executable('swifttest', 'prog.swift')) diff --git a/test cases/swift/1 exe/prog.swift b/test cases/swift/1 exe/prog.swift new file mode 100644 index 000000000..1b489de72 --- /dev/null +++ b/test cases/swift/1 exe/prog.swift @@ -0,0 +1 @@ +print("Swift executable is working.") diff --git a/test cases/swift/2 multifile/libfile.swift b/test cases/swift/2 multifile/libfile.swift new file mode 100644 index 000000000..45f941ca7 --- /dev/null +++ b/test cases/swift/2 multifile/libfile.swift @@ -0,0 +1,3 @@ +func printSomething(text: String) { + print("Got this: \(text)") +} diff --git a/test cases/swift/2 multifile/main.swift b/test cases/swift/2 multifile/main.swift new file mode 100644 index 000000000..61d67e2fb --- /dev/null +++ b/test cases/swift/2 multifile/main.swift @@ -0,0 +1 @@ +printSomething("String from main") diff --git a/test cases/swift/2 multifile/meson.build b/test cases/swift/2 multifile/meson.build new file mode 100644 index 000000000..9012f3d41 --- /dev/null +++ b/test cases/swift/2 multifile/meson.build @@ -0,0 +1,3 @@ +project('2 files', 'swift') + +test('2files', executable('twofiles', 'main.swift', 'libfile.swift')) diff --git a/test cases/swift/3 library/exe/main.swift b/test cases/swift/3 library/exe/main.swift new file mode 100644 index 000000000..c5de3739b --- /dev/null +++ b/test cases/swift/3 library/exe/main.swift @@ -0,0 +1,7 @@ +import DataSource + +let data = getData() +let data2 = getOther() + +print("String from module: \(data)") +print("Other string: \(data2)") diff --git a/test cases/swift/3 library/exe/meson.build b/test cases/swift/3 library/exe/meson.build new file mode 100644 index 000000000..6c13957e5 --- /dev/null +++ b/test cases/swift/3 library/exe/meson.build @@ -0,0 +1,2 @@ +exe = executable('dataprog', 'main.swift', link_with : datasource) +test('dataprog', exe) diff --git a/test cases/swift/3 library/lib/datasource.swift b/test cases/swift/3 library/lib/datasource.swift new file mode 100644 index 000000000..4ac7c2ab8 --- /dev/null +++ b/test cases/swift/3 library/lib/datasource.swift @@ -0,0 +1,3 @@ +public func getData() -> String { + return "String from module." +} diff --git a/test cases/swift/3 library/lib/meson.build b/test cases/swift/3 library/lib/meson.build new file mode 100644 index 000000000..fc65556d8 --- /dev/null +++ b/test cases/swift/3 library/lib/meson.build @@ -0,0 +1 @@ +datasource = static_library('DataSource', 'datasource.swift', 'othersource.swift') diff --git a/test cases/swift/3 library/lib/othersource.swift b/test cases/swift/3 library/lib/othersource.swift new file mode 100644 index 000000000..fb668baa5 --- /dev/null +++ b/test cases/swift/3 library/lib/othersource.swift @@ -0,0 +1,3 @@ +public func getOther() -> String { + return "String from other source." +} diff --git a/test cases/swift/3 library/meson.build b/test cases/swift/3 library/meson.build new file mode 100644 index 000000000..d601721f0 --- /dev/null +++ b/test cases/swift/3 library/meson.build @@ -0,0 +1,4 @@ +project('linking', 'swift') + +subdir('lib') +subdir('exe') diff --git a/test cases/swift/4 generate/gen/main.swift b/test cases/swift/4 generate/gen/main.swift new file mode 100644 index 000000000..2f2bd3683 --- /dev/null +++ b/test cases/swift/4 generate/gen/main.swift @@ -0,0 +1,10 @@ +import Glibc + +let fname = Process.arguments[1] +let code = "public func getGenerated() -> Int {\n return 42\n}\n" + +let f = fopen(fname, "w") + +fwrite(code, 1, Int(strlen(code)), f) +print("Name: \(fname)") +fclose(f) diff --git a/test cases/swift/4 generate/gen/meson.build b/test cases/swift/4 generate/gen/meson.build new file mode 100644 index 000000000..8cd7e0463 --- /dev/null +++ b/test cases/swift/4 generate/gen/meson.build @@ -0,0 +1,6 @@ +gen = executable('gen', 'main.swift') + +srcs = custom_target('gensrc', + output : 'gen.swift', + command : [gen, '@OUTPUT@'] +) diff --git a/test cases/swift/4 generate/meson.build b/test cases/swift/4 generate/meson.build new file mode 100644 index 000000000..ccc7d5c82 --- /dev/null +++ b/test cases/swift/4 generate/meson.build @@ -0,0 +1,4 @@ +project('swift generator', 'swift') + +subdir('gen') +subdir('user') diff --git a/test cases/swift/4 generate/user/main.swift b/test cases/swift/4 generate/user/main.swift new file mode 100644 index 000000000..e6b46cd19 --- /dev/null +++ b/test cases/swift/4 generate/user/main.swift @@ -0,0 +1,3 @@ +let generated = getGenerated() + +print("Generated number is: \(generated).") diff --git a/test cases/swift/4 generate/user/meson.build b/test cases/swift/4 generate/user/meson.build new file mode 100644 index 000000000..fc4722d4c --- /dev/null +++ b/test cases/swift/4 generate/user/meson.build @@ -0,0 +1,2 @@ +user = executable('user', 'main.swift', srcs) +test('User test', user) diff --git a/test cases/swift/5 mixed/main.swift b/test cases/swift/5 mixed/main.swift new file mode 100644 index 000000000..557412687 --- /dev/null +++ b/test cases/swift/5 mixed/main.swift @@ -0,0 +1,3 @@ +let num = getNumber() + +print("The number returned from C code is: \(num).") diff --git a/test cases/swift/5 mixed/meson.build b/test cases/swift/5 mixed/meson.build new file mode 100644 index 000000000..71cb99d52 --- /dev/null +++ b/test cases/swift/5 mixed/meson.build @@ -0,0 +1,6 @@ +project('mixed', 'c', 'swift') + +lib = static_library('mylib', 'mylib.c') +exe = executable('prog', 'main.swift', 'mylib.h', + link_with : lib) +test('c interface', exe) diff --git a/test cases/swift/5 mixed/mylib.c b/test cases/swift/5 mixed/mylib.c new file mode 100644 index 000000000..e091836bc --- /dev/null +++ b/test cases/swift/5 mixed/mylib.c @@ -0,0 +1,5 @@ +#include"mylib.h" + +int getNumber() { + return 42; +} diff --git a/test cases/swift/5 mixed/mylib.h b/test cases/swift/5 mixed/mylib.h new file mode 100644 index 000000000..21bd9eb0b --- /dev/null +++ b/test cases/swift/5 mixed/mylib.h @@ -0,0 +1,3 @@ +#pragma once + +int getNumber();