blob: 68f36bbd81f4b8c3fc6efd1df5d213d7124130bf [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
from distutils.core import setup, Extension
from distutils.command.build import build as _build
from distutils.command.clean import clean as _clean
from distutils.cmd import Command as _Command
from distutils.dep_util import newer_group
import distutils.ccompiler
import os
import glob
import platform
import re
import shutil
import subprocess
import sysconfig
# Get a compiler object and and strings representing the compiler type and
# CFLAGS.
compiler = distutils.ccompiler.new_compiler()
cflags = sysconfig.get_config_var('CFLAGS')
compiler_type = distutils.ccompiler.get_default_compiler()
# There's no public way to get a string representing the compiler executable
# out of distutils, but the member variable has been in the same place for a
# long time, so violating encapsulation may be ok.
compiler_name = " ".join(compiler.compiler)
BASE_DIR = os.path.abspath(os.path.join(os.pardir, os.pardir, os.pardir))
PARENT_DIR = os.path.abspath(os.pardir)
CFC_SOURCE_DIR = os.path.join(PARENT_DIR, 'src')
CFC_INCLUDE_DIR = os.path.join(PARENT_DIR, 'include')
COMMON_SOURCE_DIR = os.path.join(PARENT_DIR, 'common')
CHARMONIZER_C = os.path.join(COMMON_SOURCE_DIR, 'charmonizer.c')
CHARMONIZER_EXE_NAME = compiler.executable_filename('charmonizer')
CHARMONIZER_EXE_PATH = os.path.join(os.curdir, CHARMONIZER_EXE_NAME)
CHARMONY_H_PATH = 'charmony.h'
LEMON_DIR = os.path.join(BASE_DIR, 'lemon')
LEMON_EXE_NAME = compiler.executable_filename('lemon')
LEMON_EXE_PATH = os.path.join(LEMON_DIR, LEMON_EXE_NAME)
# Accumulate lists of source files and target files.
c_filepaths = []
y_filepaths = []
paths_to_clean = [
CHARMONIZER_EXE_PATH,
CHARMONY_H_PATH,
'_charm*',
]
c_filepaths.append(os.path.join('clownfish', '_cfc.c'))
for (dirpath, dirnames, files) in os.walk(CFC_SOURCE_DIR):
for filename in files:
if filename.endswith('.y'):
path = os.path.join(dirpath, filename)
y_filepaths.append(path)
path = re.sub(r'y$', 'h', path)
paths_to_clean.append(path)
path = re.sub(r'h$', 'c', path)
paths_to_clean.append(path)
c_filepaths.append(path)
path = compiler.object_filenames([path])[0]
paths_to_clean.append(path)
if filename.endswith('.c'):
path = os.path.join(dirpath, filename)
c_filepaths.append(path)
path = compiler.object_filenames([path])[0]
paths_to_clean.append(path)
def _quotify(text):
text = text.replace('\\', '\\\\')
text = text.replace('"', '\\"')
return '"' + text + '"'
def _run_make(command=[], directory=None):
current_directory = os.getcwd();
if (directory != None):
os.chdir(directory)
if (compiler_type == 'msvc'):
command.insert(0, 'Makefile.MSVC')
command.insert(0, '-f')
elif (platform.system() == 'Windows'):
command.insert(0, 'Makefile.MinGW')
command.insert(0, '-f')
command.insert(0, "make")
subprocess.check_call(command)
if (directory != None):
os.chdir(current_directory)
class charmony(_Command):
description = "Build and run charmonizer"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
# Compile charmonizer.
if newer_group([CHARMONIZER_C], CHARMONIZER_EXE_PATH):
command = [compiler_name]
if compiler_type == 'msvc':
command.append('/Fe' + CHARMONIZER_EXE_PATH)
else:
command.extend(['-o', CHARMONIZER_EXE_PATH])
command.append(CHARMONIZER_C)
print(" ".join(command))
subprocess.check_call(command)
# Run charmonizer.
if newer_group([CHARMONIZER_EXE_PATH], CHARMONY_H_PATH):
command = [
CHARMONIZER_EXE_PATH,
'--cc=' + _quotify(compiler_name),
'--enable-c',
'--',
cflags
]
if 'CHARM_VALGRIND' in os.environ:
command[0:0] = "valgrind", "--leak-check=yes";
print(" ".join(command))
subprocess.check_call(command)
class lemon(_Command):
description = "Compile the Lemon parser generator"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
if not os.path.exists(LEMON_EXE_PATH):
_run_make(['CC=' + _quotify(compiler_name)], directory=LEMON_DIR)
class parsers(_Command):
description = "Run .y files through lemon"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
for y_path in y_filepaths:
target = re.sub(r'y$', 'c', y_path)
if newer_group([y_path], target):
command = [LEMON_EXE_PATH, '-c', y_path]
subprocess.check_call(command)
class my_clean(_clean):
def run(self):
_clean.run(self)
_run_make(command=['clean'], directory=LEMON_DIR)
for elem in paths_to_clean:
for path in glob.glob(elem):
print("removing " + path)
if os.path.isdir(path):
shutil.rmtree(path)
else:
os.unlink(path)
class my_build(_build):
def run(self):
self.run_command('charmony')
self.run_command('lemon')
self.run_command('parsers')
_build.run(self)
cfc_extension = Extension('clownfish._cfc',
define_macros = [('CFCPYTHON', None)],
include_dirs = [
CFC_INCLUDE_DIR,
CFC_SOURCE_DIR,
os.curdir,
],
sources = c_filepaths)
setup(name = 'clownfish-cfc',
version = '0.4.1',
description = 'Clownfish compiler',
author = 'Apache Lucy Project',
author_email = 'dev at lucy dot apache dot org',
url = 'http://lucy.apache.org',
packages = ['clownfish.cfc',
],
cmdclass = {
'build': my_build,
'clean': my_clean,
'lemon': lemon,
'charmony': charmony,
'parsers': parsers,
},
ext_modules = [cfc_extension])