2014-10-07 20:24:12 +00:00
#!/usr/bin/env python
2014-08-29 08:34:44 +00:00
# -*- coding: utf-8 -*-
2014-08-29 21:45:13 +00:00
#
# Copyright (c) 2014 Microsoft Corporation. All rights reserved.
# Released under Apache 2.0 license as described in the file LICENSE.
#
# Author: Soonho Kong
#
2014-09-09 16:46:39 +00:00
import argparse
2014-08-29 08:34:44 +00:00
import fnmatch
2014-09-09 16:46:39 +00:00
import glob
2014-09-30 21:20:48 +00:00
import logging
import logging.handlers
2014-08-29 08:34:44 +00:00
import os
import platform
2014-09-09 16:46:39 +00:00
import shutil
2014-08-29 08:34:44 +00:00
import stat
import subprocess
import sys
2014-09-09 16:46:39 +00:00
import tempfile
2014-08-29 08:34:44 +00:00
import threading
import urllib
2014-10-16 00:04:23 +00:00
# Enforce subprocesses to use 'utf-8'
reload(sys)
sys.setdefaultencoding("utf-8")
2014-10-04 00:28:00 +00:00
# Fixate the path separator as '\' on Windows Platform
# even if users are on CYGWIN/MSYS2 environment
if platform.system() == "Windows":
os.path.sep = "\\"
2014-10-04 00:30:29 +00:00
# Reset MSYSTEM environment variable to enforce native-WINDOWS
# behavior to subprocesses.
if "MSYSTEM" in os.environ:
del os.environ["MSYSTEM"]
2014-10-07 18:53:09 +00:00
2014-10-03 00:05:39 +00:00
if platform.system().startswith("MSYS"):
# In MSYS platform, realpath has a strange behavior.
# os.path.realpath("c:\a\b\c") => \:\a\b\c
g_linja_path = os.path.abspath(os.path.normpath(__file__))
else:
g_linja_path = os.path.abspath(os.path.realpath(__file__))
2014-09-20 01:05:59 +00:00
g_lean_bin_dir = os.path.dirname(g_linja_path)
2014-10-07 18:53:09 +00:00
g_phony_targets = ["all", "clean", "tags", "clear-cache"]
2014-09-20 01:05:59 +00:00
g_project_filename = ".project"
g_lean_path = "USE DEFAULT" # System will search automatically
g_ltags_path = "USE DEFAULT" # System will search automatically
g_ninja_path = "USE DEFAULT" # System will search automatically
2014-09-30 21:20:48 +00:00
g_flycheck_header = "FLYCHECK_BEGIN"
g_flycheck_footer = "FLYCHECK_END"
g_logger = logging.getLogger('linja_logger')
g_debug_mode = False
2014-10-16 00:04:23 +00:00
class FlycheckItem:
def __init__(self, filename, lineno, colno, ty, msg):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.ty = ty
self.msg = msg
pass
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
ret = g_flycheck_header + " " + self.ty.upper() + "\n"
ret += "%s:%d:%d: %s: %s" % (self.filename, self.lineno, self.colno, self.ty, self.msg) + "\n"
ret += g_flycheck_footer
return ret
def __lt__(self, other):
return (self.filename, self.ty, self.lineno, self.colno) < (other.filename, other.ty, other.lineno, other.colno)
def loc(self):
return (self.filename, self.ty, self.lineno, self.colno)
@classmethod
def fromString(cls, text):
# Throw the first and last lines (header/footer)
lines = text.splitlines()
lines = lines[1:-1]
firstLine = lines[0]
items = [item.strip() for item in firstLine.split(":")]
filename = items[0]
lineno = int(items[1])
colno = int(items[2])
ty = items[3]
msg = ":".join(items[4:]) + "\n" + "\n".join(lines[1:])
msg = msg.strip()
return cls(filename, lineno, colno, ty, msg)
class FlycheckItemList:
def __init__(self, items):
self.items = items
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return "\n".join([unicode(item) for item in self.items])
def __getitem__(self, i):
return self.items[i]
def __len__(self):
return len(self.items)
def sort(self):
self.items = sorted(self.items)
def filter(self, pred):
self.items = filter(pred, self.items)
def append(self, item):
self.items.append(item)
2014-11-28 23:21:56 +00:00
def truncate(self, n):
del self.items[n:]
2014-10-16 00:04:23 +00:00
def removeExtraItemsStartswith(self, text):
self.sort()
newItems = self.items[:1]
i = 1
while i < len(self.items):
prev_item = self.items[i-1]
cur_item = self.items[i]
if not cur_item.msg.startswith(text) or prev_item.loc() != cur_item.loc():
newItems.append(cur_item)
i += 1
self.items = newItems
@classmethod
def fromString(cls, text):
items = []
tmpBuffer = ""
ignore = True
for line in text.splitlines():
if line.startswith(g_flycheck_header):
tmpBuffer = tmpBuffer + line + "\n"
ignore = False
elif line.startswith(g_flycheck_footer):
tmpBuffer = tmpBuffer + line + "\n"
items.append(FlycheckItem.fromString(tmpBuffer.strip()))
tmpBuffer = ""
ignore = True
elif not ignore:
tmpBuffer = tmpBuffer + line + "\n"
return cls(items)
2014-09-30 21:20:48 +00:00
def init_logger():
formatter = logging.Formatter('[%(levelname)s] %(asctime)s %(message)s')
streamHandler = logging.StreamHandler()
streamHandler.setFormatter(formatter)
g_logger.addHandler(streamHandler)
if g_debug_mode == True:
fileHandler = logging.FileHandler('./linja.log')
2014-10-07 18:53:09 +00:00
fileHandler.setFormatter(formatter)
2014-09-30 21:20:48 +00:00
g_logger.addHandler(fileHandler)
2014-08-29 08:34:44 +00:00
def log(msg):
print >> sys.stderr, msg
def log_nonewline(msg):
print >> sys.stderr, ("\r%s" % msg),
sys.stderr.flush()
def error(msg):
log("Error: %s" % msg)
exit(1)
2014-09-09 16:46:39 +00:00
class LinjaException(Exception):
"""Custom Exception"""
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
2014-08-29 08:34:44 +00:00
def give_exec_permission(filename):
2014-09-20 01:05:59 +00:00
"""chmod +x filename"""
2014-08-29 08:34:44 +00:00
st = os.stat(filename)
os.chmod(filename, st.st_mode | stat.S_IEXEC)
def show_download_progress(*data):
file_size = int(data[2]/1000)
total_packets = data[2]/data[1]
downloaded_packets = data[0]
log_nonewline("Download: size\t= %i kb, packet: %i/%i" % (file_size, downloaded_packets, total_packets+1))
2014-09-04 21:03:36 +00:00
def get_ninja_url():
prefix = "https://leanprover.github.io/bin/"
if platform.architecture()[0] == "64bit":
if platform.system() == "Linux":
return prefix + "ninja-1.5.1-linux-x86_64"
2014-09-04 21:36:42 +00:00
elif platform.system() == "Windows":
2014-09-04 21:03:36 +00:00
return prefix + "ninja-1.5.1-win.exe"
elif platform.system() == "Darwin":
return prefix + "ninja-1.5.1-osx"
if platform.architecture()[0] == "32bit":
if platform.system() == "Linux":
return prefix + "ninja-1.5.1-linux-i386"
2014-09-04 21:36:42 +00:00
elif platform.system() == "Windows":
2014-09-04 21:03:36 +00:00
pass # TODO(soonhok): add support
elif platform.system() == "Darwin":
pass # TODO(soonhok): add support
error("we do not have ninja executable for this platform: %s" % platform.platform())
2014-09-09 16:46:39 +00:00
def build_ninja_and_save_at(ninja_path, platform):
saved_current_dir = os.getcwd()
tempdir = tempfile.mkdtemp()
build_dir = os.path.join(tempdir, "ninja")
built_ninja_path = os.path.join(build_dir, "ninja")
cmd_clone_ninja = ["git", "clone", "git://github.com/martine/ninja.git"]
cmd_checkout_release = ["git", "checkout", "release"]
cmd_bootstrap = [os.path.join(build_dir, "bootstrap.py"), "--platform", platform]
try:
os.chdir(tempdir)
if subprocess.call(cmd_clone_ninja):
raise LinjaException("Failed to clone ninja repository")
2014-09-19 16:41:50 +00:00
os.chdir(build_dir)
2014-09-09 16:46:39 +00:00
if subprocess.call(cmd_checkout_release):
raise LinjaException("Failed to checkout release branch of ninja")
if subprocess.call(cmd_bootstrap):
raise LinjaException("Failed to build ninja")
shutil.copy2(built_ninja_path, ninja_path)
except IOError as e:
error(e)
except LinjaException as e:
error(e)
finally:
os.chdir(saved_current_dir)
shutil.rmtree(tempdir)
2014-08-29 08:34:44 +00:00
return ninja_path
2014-09-09 16:46:39 +00:00
def download_ninja_and_save_at(ninja_path):
if platform.system().startswith("CYGWIN"):
return build_ninja_and_save_at(ninja_path, "linux")
else:
url = get_ninja_url()
log("Downloading ninja: %s ===> %s\n" % (url, ninja_path))
urllib.urlretrieve(url, ninja_path, show_download_progress)
log("\n")
if not os.path.isfile(ninja_path):
error("failed to download ninja executable from %s" % url)
2014-09-19 16:41:50 +00:00
give_exec_permission(ninja_path)
2014-09-09 16:46:39 +00:00
return ninja_path
2014-09-20 01:05:59 +00:00
def find_file_upward(name, path = os.getcwd()):
project_file = os.path.join(path, name)
if os.path.isfile(project_file):
return path
up = os.path.dirname(path)
if up != path:
return find_file_upward(name, up)
return None
def escape_ninja_char(name):
return name.replace(":", "$:")
def normalize_drive_name(name):
if platform.system() == "Windows":
drive, path = os.path.splitdrive(name)
if drive == None:
return name
else:
2014-09-20 17:51:29 +00:00
# Leo: return drive.lower() + path
return path
2014-09-20 01:05:59 +00:00
else:
return name
def process_target(target):
if target in g_phony_targets:
return target
return normalize_drive_name(os.path.abspath(target))
def which(program):
""" Lookup program in a path """
import os
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
def find_location(exec_name):
pathname = which(exec_name) or os.path.join(g_lean_bin_dir, exec_name)
pathname = os.path.abspath(pathname)
return pathname
def check_requirements():
2014-08-30 21:54:32 +00:00
global g_lean_path, g_ltags_path, g_ninja_path
2014-09-19 16:41:50 +00:00
ltags_exec_name = "ltags"
2014-09-20 01:05:59 +00:00
lean_exec_name = "lean"
ninja_exec_name = "ninja"
if platform.system() == "Windows" or platform.system().startswith("MSYS"):
lean_exec_name = "lean.exe"
ninja_exec_name = "ninja.exe"
2014-08-29 08:34:44 +00:00
if g_lean_path == "USE DEFAULT":
2014-09-20 01:05:59 +00:00
g_lean_path = find_location(lean_exec_name)
2014-08-30 21:54:32 +00:00
if g_ltags_path == "USE DEFAULT":
2014-09-20 01:05:59 +00:00
g_ltags_path = find_location(ltags_exec_name)
2014-08-29 08:34:44 +00:00
if g_ninja_path == "USE DEFAULT":
2014-09-20 01:05:59 +00:00
g_ninja_path = find_location(ninja_exec_name)
2014-08-29 08:34:44 +00:00
if not os.path.isfile(g_lean_path):
2014-10-03 00:05:28 +00:00
error("cannot find lean executable at " + os.path.abspath(g_lean_path))
2014-08-30 21:54:32 +00:00
if not os.path.isfile(g_ltags_path):
2014-09-19 16:41:50 +00:00
error("cannot find ltags executable at " + os.path.abspath(g_ltags_path))
2014-08-29 08:34:44 +00:00
if not os.path.isfile(g_ninja_path):
g_ninja_path = download_ninja_and_save_at(g_ninja_path)
2014-09-20 01:05:59 +00:00
def process_args(args):
if (args.flycheck == False and args.flycheck_max_messages != None):
error("Please use --flycheck option with --flycheck-max-messages option.")
args.targets = map(process_target, args.targets)
if args.directory:
os.chdir(args.directory)
args.project_dir = find_file_upward(g_project_filename)
if args.project_dir:
os.chdir(args.project_dir)
if args.cache:
args.cache = process_target(args.cache)
if len(args.targets) != 1:
error("--cache option can only be used with one target")
2014-12-05 21:09:23 +00:00
if not args.cache.endswith(".lean") and not args.cache.endswith(".hlean"):
error("cache argument has to end with .lean or .hlean")
if args.cache.endswith(".lean"):
args.cache = args.cache[:-4] + "clean"
else:
args.cache = args.cache[:-5] + "clean"
2014-09-20 01:05:59 +00:00
args.phony_targets = list(set(g_phony_targets) & set(args.targets))
2014-09-30 21:20:48 +00:00
if args.verbose:
g_logger.setLevel(logging.INFO)
2014-09-20 01:05:59 +00:00
return args
2014-08-29 08:34:44 +00:00
2014-09-20 01:05:59 +00:00
def get_lean_options(args):
args.lean_options = []
if args.flycheck:
args.lean_options.append("--flycheck")
2014-10-15 01:56:17 +00:00
if args.discard:
args.lean_options.append("--discard")
2015-01-16 00:47:34 +00:00
if args.memory:
args.lean_options += ["-M", args.memory]
if args.Trust:
args.lean_options.append("--Trust")
2014-10-15 01:56:17 +00:00
if args.to_axiom:
args.lean_options.append("--to_axiom")
2014-09-20 01:05:59 +00:00
if args.cache:
args.lean_options += ["-c", args.cache]
if args.lean_config_option:
for item in args.lean_config_option:
args.lean_options.append("-D" + item)
return args
2014-08-29 08:34:44 +00:00
2014-09-20 01:05:59 +00:00
def parse_arg(argv):
parser = argparse.ArgumentParser(description='linja: ninja build wrapper for Lean theorem prover.')
parser.add_argument('--flycheck', '-F', action='store_true', default=False, help="Use --flycheck option for Lean.")
parser.add_argument('--flycheck-max-messages', action='store', type=int, default=None, const=999999, nargs='?', help="Number of maximum flycheck messages to display.")
parser.add_argument('--cache', action='store', help="Use specified cache (clean) file.")
parser.add_argument('--directory', '-C', action='store', help="change to DIR before doing anything else.")
parser.add_argument('--lean-config-option', '-D', action='append', help="set a Lean configuration option (name=value)")
2014-09-30 21:20:48 +00:00
parser.add_argument('--verbose', '-v', action='store_true', help="turn on verbose option")
2015-01-16 00:47:34 +00:00
parser.add_argument('--memory', '-M', action='store', default=None, const=1, nargs='?', help="maximum amount of memory that can be used by Lean [default=unbounded]")
2014-10-08 17:22:58 +00:00
parser.add_argument('--keep-going', '-k', action='store', default=None, const=1, nargs='?', help="keep going until N jobs fail [default=1]")
2015-01-16 00:47:34 +00:00
parser.add_argument('--discard', '-r', action='store_true', default=False, help="discard the proof of imported theorems after checking")
2014-10-15 01:56:17 +00:00
parser.add_argument('--to_axiom', '-X', action='store_true', default=False, help="discard proofs of all theorems after checking them, i.e., theorems become axioms after checking")
2015-01-16 00:47:34 +00:00
parser.add_argument('--Trust', '-T', action='store_true', default=False, help="short-cut for maximum trust level (e.g., Lean will not type check imported files)")
2014-09-20 01:05:59 +00:00
parser.add_argument('targets', nargs='*')
args = parser.parse_args(argv)
check_requirements()
args = process_args(args)
args = get_lean_options(args)
return args
2014-08-29 08:34:44 +00:00
2014-09-20 01:05:59 +00:00
def debug_status(args):
print "Working Directory =", os.getcwd()
print ""
for key, val in vars(args).iteritems():
print "Option[" + key + "] =", val
print ""
print "linja path =", g_linja_path
print "lean/bin dir =", g_lean_bin_dir
print "phony targets =", g_phony_targets
print "lean path =", g_lean_path
print "ltags path =", g_ltags_path
print "ninja path =", g_ninja_path
def handle_flycheck_failure(out, err, args):
2014-09-09 17:51:24 +00:00
if len(args.targets) == 0:
2014-09-20 01:05:59 +00:00
error("handle_flycheck_failure is called without targets")
2014-09-09 17:51:24 +00:00
target = args.targets[0]
2014-08-29 08:34:44 +00:00
failed = set()
2014-09-19 16:41:50 +00:00
for line in out.splitlines():
2014-08-29 08:34:44 +00:00
if line.startswith("FAILED:"):
2014-09-20 01:05:59 +00:00
for lean_file in find_lean_files(args):
2014-08-29 08:34:44 +00:00
lean = lean_file['lean']
2014-08-29 21:46:35 +00:00
if lean in line and lean != target:
2014-08-29 08:34:44 +00:00
failed.add(lean)
for failed_file in failed:
2014-09-20 01:05:59 +00:00
print g_flycheck_header, "ERROR"
2014-08-29 21:46:35 +00:00
print "%s:1:0: error: failed to compile %s" % (target, failed_file)
2014-09-20 01:05:59 +00:00
print g_flycheck_footer
2014-09-14 06:36:13 +00:00
if err:
2014-09-20 01:05:59 +00:00
print g_flycheck_header, "ERROR"
2014-09-14 06:36:13 +00:00
print "%s:1:0: error: %s" % (target, err.strip())
2014-09-20 01:05:59 +00:00
print g_flycheck_footer
2014-08-29 21:47:13 +00:00
if failed:
2014-09-09 17:51:24 +00:00
call_lean(target, args)
2014-08-29 21:47:13 +00:00
2014-12-05 21:09:23 +00:00
def process_lean_output(target, out, args, using_hlean):
2014-09-20 01:05:59 +00:00
n = args.flycheck_max_messages
2014-09-09 01:43:57 +00:00
if target.endswith(".olean"):
2014-12-05 21:09:23 +00:00
if using_hlean:
target = target[:-5] + "hlean"
else:
target = target[:-5] + "lean"
if (not target.endswith(".lean") and not using_hlean) or (not target.endswith(".hlean") and using_hlean):
2014-09-09 01:43:57 +00:00
print out
return
2014-10-16 00:04:23 +00:00
# Parse, filter, and remove extra items
flycheckItemList = FlycheckItemList.fromString(out)
flycheckItemList.filter(lambda item: item.filename == target)
flycheckItemList.removeExtraItemsStartswith("failed to add declaration")
# Only keep n items in the list.
# Add tooManyItemsError at the end if we truncated the list
if n and len(flycheckItemList) > n:
count = len(flycheckItemList)
2014-11-28 23:21:56 +00:00
flycheckItemList.truncate(n)
2014-10-16 00:04:23 +00:00
tooManyItemsError = FlycheckItem(target, 1, 0, "error", "For performance, we only display %d errors/warnings out of %d. (lean-flycheck-max-messages-to-display)" % (n, count))
flycheckItemList.append(tooManyItemsError)
print flycheckItemList
2014-09-09 01:43:57 +00:00
2014-09-20 01:05:59 +00:00
def call_ninja(args):
2014-08-29 08:34:44 +00:00
targets = []
for item in args.targets:
if item.endswith(".lean"):
targets.append(item[:-4] + "olean")
2014-12-05 21:09:23 +00:00
elif item.endswith(".hlean"):
targets.append(item[:-5] + "olean")
2014-08-29 08:34:44 +00:00
else:
targets.append(item)
proc_out = proc_err = None
if args.flycheck:
proc_out = subprocess.PIPE
proc_err = subprocess.PIPE
2014-10-08 17:22:58 +00:00
ninja_option = []
if args.keep_going:
ninja_option += ["-k", args.keep_going]
proc = subprocess.Popen([g_ninja_path] + ninja_option + targets, stdout=proc_out, stderr=proc_err)
2014-09-30 21:21:02 +00:00
(out, err) = proc.communicate()
2014-08-29 08:34:44 +00:00
if args.flycheck:
2014-12-05 21:09:23 +00:00
if len(args.targets) == 1 and (args.targets[0].endswith(".lean") or args.targets[0].endswith(".hlean")):
process_lean_output(targets[0], out, args, args.targets[0].endswith(".hlean"))
2014-09-20 01:05:59 +00:00
handle_flycheck_failure(out, err, args)
2014-09-09 01:43:57 +00:00
else:
2014-09-14 06:36:13 +00:00
print out + err
2014-09-09 01:43:57 +00:00
2014-09-02 16:52:52 +00:00
return proc.returncode
2014-09-09 17:51:24 +00:00
def call_lean(filename, args):
2014-09-20 01:05:59 +00:00
proc = subprocess.Popen([g_lean_path] + args.lean_options + [filename],
2014-09-02 16:52:52 +00:00
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
2014-09-09 01:43:57 +00:00
out = proc.communicate()[0]
2014-12-05 21:09:23 +00:00
process_lean_output(filename, out, args, filename.endswith(".hlean"))
2014-09-02 16:52:52 +00:00
return proc.returncode
2014-08-29 08:34:44 +00:00
2014-12-05 21:09:23 +00:00
def get_lean_names(lean_file, args, using_hlean):
2014-09-20 01:05:59 +00:00
lean_file = os.path.abspath(lean_file)
basename, ext = os.path.splitext(lean_file)
basename = normalize_drive_name(basename)
item = {"base" : basename}
2014-12-05 21:09:23 +00:00
lean_name_exts = ["lean", "olean", "clean", "ilean", "d"]
2014-09-20 01:05:59 +00:00
for ext in lean_name_exts:
2014-12-05 21:09:23 +00:00
if ext == "lean" and using_hlean:
item[ext] = basename + ".hlean"
else:
item[ext] = basename + "." + ext
2014-09-20 01:05:59 +00:00
if args.cache and len(args.targets) == 1 and item['lean'] == args.targets[0]:
item['clean'] = args.cache
return item
2014-08-29 08:34:44 +00:00
def find_files(directory, pattern):
if "/" in pattern:
return glob.glob(os.path.join(directory, pattern))
matches = []
for root, dirnames, filenames in os.walk(directory):
for filename in fnmatch.filter(filenames, pattern):
2014-09-20 01:05:59 +00:00
matches.append(normalize_drive_name(os.path.join(root, filename)))
2014-08-29 08:34:44 +00:00
return matches
2014-09-20 01:05:59 +00:00
def find_lean_files(args):
"""Find lean files under project directory. Include and exclude
files based on patterns in .project file. Use static cache to
compute only once and reuse it"""
project_dir = args.project_dir
if find_lean_files.cached_list != []:
return find_lean_files.cached_list
2014-08-29 08:34:44 +00:00
files = set()
include_patterns, exclude_patterns = [], []
2014-09-20 01:05:59 +00:00
with open(os.path.join(project_dir, g_project_filename), 'r') as f:
2014-08-29 08:34:44 +00:00
for line in f:
c = line[0]
if c == '+':
include_patterns.append(line[1:].strip())
elif c == '-':
exclude_patterns.append(line[1:].strip())
elif c == '#':
pass # Comment
elif c == 'T':
pass # TAG
elif c == 'O':
pass # Lean Option
for pattern in include_patterns:
files |= set(find_files(project_dir, pattern))
for pattern in exclude_patterns:
files -= set(find_files(project_dir, pattern))
2014-12-05 21:09:23 +00:00
has_lean = False
has_hlean = False
2014-12-05 22:38:45 +00:00
for file in files:
2014-08-29 08:34:44 +00:00
if file.endswith(".lean"):
2014-12-05 21:09:23 +00:00
has_lean = True
if file.endswith(".hlean"):
has_hlean = True
if has_lean and has_hlean:
error("project cannot mix .lean and .hlean files")
for file in args.targets:
if file.endswith(".lean") or file.endswith(".hlean"):
2014-08-29 08:34:44 +00:00
files.add(file)
elif file.endswith(".olean"):
2014-12-05 21:09:23 +00:00
if has_hlean:
file.add(file[:-5] + "hlean")
else:
file.add(file[:-5] + "lean")
2014-08-29 08:34:44 +00:00
for f in files:
2014-12-05 21:09:23 +00:00
find_lean_files.cached_list.append(get_lean_names(f, args, has_hlean))
2014-09-20 01:05:59 +00:00
return find_lean_files.cached_list
# Initialize static variable
find_lean_files.cached_list = []
def clear_cache(args):
files = find_lean_files(args)
files = find_lean_files(args)
files = find_lean_files(args)
files = find_lean_files(args)
num_of_files = len(files)
i = 0
for item in files:
i += 1
clean_file = item['clean']
if os.path.isfile(clean_file):
sys.stderr.write("[%i/%i] clear cache... % -80s\r" % (i, num_of_files, clean_file))
sys.stderr.flush()
os.remove(clean_file)
if num_of_files > 0:
sys.stderr.write("\n")
2014-08-29 08:34:44 +00:00
2014-09-20 01:05:59 +00:00
def build_olean(lean, olean, clean, dlean, ilean, base):
(lean, olean, clean, dlean, ilean, base) = map(escape_ninja_char, (lean, olean, clean, dlean, ilean, base))
if clean.startswith(base):
str = """build %s %s %s: LEAN %s | %s\n""" % (olean, ilean, clean, lean, dlean)
else:
str = """build %s %s: LEAN %s | %s\n""" % (olean, ilean, lean, dlean)
str += " DLEAN_FILE=%s\n" % dlean
str += " OLEAN_FILE=%s\n" % olean
str += " CLEAN_FILE=%s\n" % clean
str += " ILEAN_FILE=%s\n" % ilean
return str
2014-09-19 16:41:50 +00:00
2014-09-20 01:05:59 +00:00
def make_build_ninja(args):
with open(os.path.join(args.project_dir, "build.ninja"), "w") as f:
lean_files = find_lean_files(args)
2014-08-29 08:34:44 +00:00
2014-09-20 01:05:59 +00:00
print >> f, """rule CLEAN"""
print >> f, """ command = """,
print >> f, """"%s" -t clean""" % g_ninja_path
print >> f, """ description = Cleaning all built files..."""
2014-08-29 08:34:44 +00:00
2014-09-20 01:05:59 +00:00
print >> f, """rule LEAN"""
print >> f, """ depfile = ${DLEAN_FILE}"""
print >> f, """ command = "%s" %s "$in" -o "${OLEAN_FILE}" -c "${CLEAN_FILE}" -i "${ILEAN_FILE}" """ \
% (g_lean_path, " ".join(args.lean_options))
2014-09-02 05:57:45 +00:00
2014-09-20 01:05:59 +00:00
print >> f, """rule LTAGS"""
print >> f, """ command = """,
if platform.system() == "Windows":
print >> f, "python ",
2014-10-07 22:07:28 +00:00
print >> f, """"%s" --relative -- $in """ % (g_ltags_path)
2014-09-19 16:41:50 +00:00
2014-09-20 01:05:59 +00:00
print >> f, "build all: phony",
for item in lean_files:
print >> f, " " + escape_ninja_char(item['olean']),
print >> f, ""
tags_file = "TAGS"
print >> f, "build tags: phony " + tags_file
print >> f, "build " + tags_file + ": LTAGS",
for item in lean_files:
print >> f, " " + escape_ninja_char(item['ilean']),
print >> f, ""
print >> f, """build clean: CLEAN"""
for item in lean_files:
print >> f, build_olean(item['lean'], item['olean'], item['clean'], item['d'], item['ilean'], item['base'])
print >> f, """default all"""
def make_deps(lean_file, dlean_file, olean_file):
with open(dlean_file, "w") as f:
deps = []
proc = subprocess.Popen([g_lean_path, "--deps", lean_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = proc.communicate()[0]
print >> f, olean_file + ": \\"
for olean_file in output.strip().splitlines():
if olean_file:
deps.append(normalize_drive_name(os.path.abspath(olean_file)))
deps_str = " " + (" \\\n ".join(deps))
print >> f, deps_str
def make_deps_all_files(args):
lean_files = find_lean_files(args)
threads = []
i, num_of_files = 1, len(lean_files)
for item in lean_files:
2014-12-05 21:09:23 +00:00
lean_file = item['lean']
dlean_file = item['d']
olean_file = item['olean']
2014-09-20 01:05:59 +00:00
if not os.path.isfile(dlean_file) or os.path.getmtime(dlean_file) < os.path.getmtime(lean_file):
thread = threading.Thread(target=make_deps, args = [lean_file, dlean_file, olean_file])
2014-10-01 00:35:20 +00:00
sys.stderr.write("[%i/%i] generating dep... % -80s" % (i, num_of_files, dlean_file))
if args.flycheck == True:
sys.stderr.write("\n")
else:
sys.stderr.write("\r")
2014-09-20 01:05:59 +00:00
sys.stderr.flush()
thread.start()
threads.append(thread)
i += 1
for thread in threads:
thread.join()
if threads != []:
sys.stderr.write("\n")
2014-09-19 16:41:50 +00:00
2014-08-29 08:34:44 +00:00
def main(argv=None):
2014-09-30 21:20:48 +00:00
init_logger()
2014-08-29 08:34:44 +00:00
if argv is None:
argv = sys.argv[1:]
args = parse_arg(argv)
2014-09-20 01:05:59 +00:00
if args.project_dir:
2014-09-02 05:57:45 +00:00
if args.targets == ["clear-cache"]:
2014-09-09 06:48:25 +00:00
args.targets = []
2014-09-20 01:05:59 +00:00
clear_cache(args)
2014-09-02 05:57:45 +00:00
return 0
2014-08-29 22:03:38 +00:00
if not "clean" in args.targets:
2014-09-20 01:05:59 +00:00
make_deps_all_files(args)
make_build_ninja(args)
return call_ninja(args)
else: # NO Project Directory Found
if args.phony_targets:
error("cannot find project directory. Make sure that you have " \
+ g_project_filename + " file at the project root.")
2014-09-02 16:52:52 +00:00
returncode = 0
for filename in args.targets:
2014-12-05 21:09:23 +00:00
if os.path.isfile(filename) and (filename.endswith(".lean") or filename.endswith(".hlean")):
2014-09-09 17:51:24 +00:00
returncode |= call_lean(filename, args)
2014-09-02 16:52:52 +00:00
return returncode
2014-08-29 08:34:44 +00:00
2014-09-20 01:05:59 +00:00
return 0
2014-08-29 08:34:44 +00:00
if __name__ == "__main__":
sys.exit(main())