meson/mesongui.py

511 lines
18 KiB
Python
Raw Normal View History

2013-09-19 02:42:18 +08:00
#!/usr/bin/env python3
# Copyright 2013 Jussi Pakkanen
# 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.
2013-09-28 03:50:06 +08:00
import sys, os, pickle, time, shutil
2013-10-19 01:15:49 +08:00
import build, coredata, environment, optinterpreter
2013-09-19 02:42:18 +08:00
from PyQt5 import uic
2013-09-28 01:01:41 +08:00
from PyQt5.QtWidgets import QApplication, QMainWindow, QHeaderView
2013-09-29 23:17:07 +08:00
from PyQt5.QtWidgets import QComboBox, QCheckBox
2013-09-28 03:50:06 +08:00
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, QVariant, QTimer
2013-09-19 03:43:33 +08:00
import PyQt5.QtCore
2013-09-30 00:35:39 +08:00
import PyQt5.QtWidgets
2013-09-19 03:43:33 +08:00
2013-10-01 02:53:47 +08:00
priv_dir = os.path.split(os.path.abspath(os.path.realpath(__file__)))[0]
2013-09-19 03:43:33 +08:00
class PathModel(QAbstractItemModel):
2013-09-19 04:01:07 +08:00
def __init__(self, coredata):
2013-09-19 03:43:33 +08:00
super().__init__()
2013-09-19 04:01:07 +08:00
self.coredata = coredata
self.names = ['Prefix', 'Library dir', 'Binary dir', 'Include dir', 'Data dir',\
'Man dir', 'Locale dir']
self.attr_name = ['prefix', 'libdir', 'bindir', 'includedir', 'datadir', \
'mandir', 'localedir']
2013-09-19 03:43:33 +08:00
def flags(self, index):
if index.column() == 1:
editable = PyQt5.QtCore.Qt.ItemIsEditable
else:
editable= 0
return PyQt5.QtCore.Qt.ItemIsSelectable | PyQt5.QtCore.Qt.ItemIsEnabled | editable
2013-09-19 04:01:07 +08:00
2013-09-19 03:43:33 +08:00
def rowCount(self, index):
if index.isValid():
return 0
2013-09-19 04:01:07 +08:00
return len(self.names)
2013-09-19 03:43:33 +08:00
def columnCount(self, index):
return 2
def headerData(self, section, orientation, role):
2013-09-28 01:01:41 +08:00
if role != PyQt5.QtCore.Qt.DisplayRole:
return QVariant()
2013-09-19 04:19:57 +08:00
if section == 1:
2013-09-19 03:43:33 +08:00
return QVariant('Path')
return QVariant('Type')
2013-09-28 01:01:41 +08:00
def index(self, row, column, parent):
return self.createIndex(row, column)
def data(self, index, role):
if role != PyQt5.QtCore.Qt.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if column == 0:
return self.names[row]
return getattr(self.coredata, self.attr_name[row])
def parent(self, index):
return QModelIndex()
def setData(self, index, value, role):
if role != PyQt5.QtCore.Qt.EditRole:
return False
row = index.row()
column = index.column()
s = str(value)
setattr(self.coredata, self.attr_name[row], s)
self.dataChanged.emit(self.createIndex(row, column), self.createIndex(row, column))
return True
2013-09-28 00:38:09 +08:00
class TargetModel(QAbstractItemModel):
def __init__(self, builddata):
super().__init__()
self.targets = []
for target in builddata.get_targets().values():
name = target.get_basename()
num_sources = len(target.get_sources()) + len(target.get_generated_sources())
if isinstance(target, build.Executable):
typename = 'executable'
elif isinstance(target, build.SharedLibrary):
typename = 'shared library'
elif isinstance(target, build.StaticLibrary):
typename = 'static library'
else:
typename = 'unknown'
2013-09-30 01:21:49 +08:00
if target.should_install():
installed = 'Yes'
else:
installed = 'No'
self.targets.append((name, typename, installed, num_sources))
2013-09-28 00:38:09 +08:00
def flags(self, index):
return PyQt5.QtCore.Qt.ItemIsSelectable | PyQt5.QtCore.Qt.ItemIsEnabled
def rowCount(self, index):
if index.isValid():
return 0
return len(self.targets)
def columnCount(self, index):
2013-09-30 01:21:49 +08:00
return 4
2013-09-28 00:38:09 +08:00
def headerData(self, section, orientation, role):
2013-09-28 01:01:41 +08:00
if role != PyQt5.QtCore.Qt.DisplayRole:
return QVariant()
2013-09-30 01:21:49 +08:00
if section == 3:
2013-09-28 01:01:41 +08:00
return QVariant('Source files')
2013-09-30 01:21:49 +08:00
if section == 2:
return QVariant('Installed')
2013-09-28 00:38:09 +08:00
if section == 1:
return QVariant('Type')
return QVariant('Name')
2013-09-19 03:43:33 +08:00
def data(self, index, role):
if role != PyQt5.QtCore.Qt.DisplayRole:
return QVariant()
2013-09-19 04:01:07 +08:00
row = index.row()
column = index.column()
2013-09-28 00:38:09 +08:00
return self.targets[row][column]
2013-09-19 03:43:33 +08:00
def index(self, row, column, parent):
return self.createIndex(row, column)
2013-09-28 01:01:41 +08:00
2013-09-19 03:43:33 +08:00
def parent(self, index):
return QModelIndex()
2013-09-19 02:42:18 +08:00
2013-09-28 01:44:31 +08:00
class DependencyModel(QAbstractItemModel):
def __init__(self, coredata):
super().__init__()
self.deps = []
for k in coredata.deps.keys():
bd = coredata.deps[k]
name = k
found = bd.found()
if found:
cflags = str(bd.get_compile_flags())
libs = str(bd.get_link_flags())
found = 'yes'
else:
cflags = ''
libs = ''
found = 'no'
self.deps.append((name, found, cflags, libs))
def flags(self, index):
return PyQt5.QtCore.Qt.ItemIsSelectable | PyQt5.QtCore.Qt.ItemIsEnabled
def rowCount(self, index):
if index.isValid():
return 0
return len(self.deps)
def columnCount(self, index):
return 4
def headerData(self, section, orientation, role):
if role != PyQt5.QtCore.Qt.DisplayRole:
return QVariant()
if section == 3:
2013-10-12 02:29:37 +08:00
return QVariant('Link flags')
2013-09-28 01:44:31 +08:00
if section == 2:
return QVariant('Compile flags')
if section == 1:
return QVariant('Found')
return QVariant('Name')
def data(self, index, role):
if role != PyQt5.QtCore.Qt.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
return self.deps[row][column]
def index(self, row, column, parent):
return self.createIndex(row, column)
def parent(self, index):
return QModelIndex()
2013-09-29 22:02:53 +08:00
class CoreModel(QAbstractItemModel):
def __init__(self, core_data):
super().__init__()
self.elems = []
for langname, comp in core_data.compilers.items():
self.elems.append((langname + ' compiler', str(comp.get_exelist())))
for langname, comp in core_data.cross_compilers.items():
self.elems.append((langname + ' cross compiler', str(comp.get_exelist())))
def flags(self, index):
return PyQt5.QtCore.Qt.ItemIsSelectable | PyQt5.QtCore.Qt.ItemIsEnabled
def rowCount(self, index):
if index.isValid():
return 0
return len(self.elems)
def columnCount(self, index):
return 2
def headerData(self, section, orientation, role):
if role != PyQt5.QtCore.Qt.DisplayRole:
return QVariant()
if section == 1:
return QVariant('Value')
return QVariant('Name')
def data(self, index, role):
if role != PyQt5.QtCore.Qt.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
return self.elems[row][column]
def index(self, row, column, parent):
return self.createIndex(row, column)
def parent(self, index):
return QModelIndex()
2013-09-29 23:17:07 +08:00
class OptionForm:
2013-10-19 01:55:10 +08:00
def __init__(self, coredata, form):
2013-10-19 01:15:49 +08:00
self.coredata = coredata
2013-09-29 23:17:07 +08:00
self.form = form
2013-10-19 01:15:49 +08:00
form.addRow(PyQt5.QtWidgets.QLabel("Meson options"))
2013-09-29 23:17:07 +08:00
combo = QComboBox()
combo.addItem('plain')
combo.addItem('debug')
2013-09-29 23:20:56 +08:00
combo.addItem('optimized')
2013-09-29 23:17:07 +08:00
combo.setCurrentText(self.coredata.buildtype)
combo.currentTextChanged.connect(self.build_type_changed)
self.form.addRow('Build type', combo)
strip = QCheckBox("")
2013-10-14 04:03:25 +08:00
strip.setChecked(self.coredata.strip)
2013-09-29 23:17:07 +08:00
strip.stateChanged.connect(self.strip_changed)
self.form.addRow('Strip on install', strip)
coverage = QCheckBox("")
2013-10-14 04:03:25 +08:00
coverage.setChecked(self.coredata.coverage)
2013-09-29 23:17:07 +08:00
coverage.stateChanged.connect(self.coverage_changed)
self.form.addRow('Enable coverage', coverage)
2013-10-14 04:03:25 +08:00
pch = QCheckBox("")
pch.setChecked(self.coredata.use_pch)
pch.stateChanged.connect(self.pch_changed)
self.form.addRow('Enable pch', pch)
2013-10-19 01:15:49 +08:00
form.addRow(PyQt5.QtWidgets.QLabel("Project options"))
self.set_user_options()
def set_user_options(self):
2013-10-19 01:55:10 +08:00
options = self.coredata.user_options
2013-10-19 01:15:49 +08:00
keys = list(options.keys())
keys.sort()
2013-10-19 01:55:10 +08:00
self.opt_keys = keys
self.opt_widgets = []
2013-10-19 01:15:49 +08:00
for key in keys:
opt = options[key]
if isinstance(opt, optinterpreter.UserStringOption):
w = PyQt5.QtWidgets.QLineEdit(opt.value)
2013-10-19 01:55:10 +08:00
w.textChanged.connect(self.user_option_changed)
2013-10-19 01:15:49 +08:00
elif isinstance(opt, optinterpreter.UserBooleanOption):
w = QCheckBox('')
w.setChecked(opt.value)
2013-10-19 01:55:10 +08:00
w.stateChanged.connect(self.user_option_changed)
2013-10-19 01:15:49 +08:00
elif isinstance(opt, optinterpreter.UserComboOption):
w = QComboBox()
for i in opt.choices:
w.addItem(i)
w.setCurrentText(opt.value)
2013-10-19 01:55:10 +08:00
w.currentTextChanged.connect(self.user_option_changed)
2013-10-19 01:15:49 +08:00
else:
raise RuntimeError("Unknown option type")
2013-10-19 01:55:10 +08:00
self.opt_widgets.append(w)
2013-10-19 01:15:49 +08:00
self.form.addRow(opt.description, w)
2013-09-29 23:17:07 +08:00
2013-10-19 01:55:10 +08:00
def user_option_changed(self, dummy=None):
for i in range(len(self.opt_keys)):
key = self.opt_keys[i]
w = self.opt_widgets[i]
if isinstance(w, PyQt5.QtWidgets.QLineEdit):
newval = w.text()
elif isinstance(w, QComboBox):
newval = w.currentText()
elif isinstance(w, QCheckBox):
if w.checkState() == 0:
newval = False
else:
newval = True
else:
raise RuntimeError('Unknown widget type')
self.coredata.user_options[key].set_value(newval)
2013-10-19 01:55:10 +08:00
2013-09-29 23:17:07 +08:00
def build_type_changed(self, newtype):
self.coredata.buildtype = newtype
def strip_changed(self, newState):
if newState == 0:
ns = False
else:
ns = True
self.coredata.strip = ns
def coverage_changed(self, newState):
if newState == 0:
ns = False
else:
ns = True
self.coredata.coverage = ns
2013-10-14 04:03:25 +08:00
def pch_changed(self, newState):
if newState == 0:
ns = False
else:
ns = True
self.coredata.use_pch = ns
2013-09-28 03:50:06 +08:00
class ProcessRunner():
def __init__(self, rundir, cmdlist):
self.cmdlist = cmdlist
2013-10-14 06:46:51 +08:00
self.ui = uic.loadUi(os.path.join(priv_dir, 'mesonrunner.ui'))
2013-09-28 03:50:06 +08:00
self.timer = QTimer(self.ui)
self.timer.setInterval(1000)
self.timer.timeout.connect(self.timeout)
self.process = PyQt5.QtCore.QProcess()
self.process.setProcessChannelMode(PyQt5.QtCore.QProcess.MergedChannels)
self.process.setWorkingDirectory(rundir)
self.process.readyRead.connect(self.read_data)
self.process.finished.connect(self.finished)
self.ui.termbutton.clicked.connect(self.terminated)
self.return_value = 100
def run(self):
2013-09-28 03:50:06 +08:00
self.process.start(self.cmdlist[0], self.cmdlist[1:])
self.timer.start()
self.start_time = time.time()
return self.ui.exec()
2013-09-28 03:50:06 +08:00
def read_data(self):
while(self.process.canReadLine()):
txt = bytes(self.process.readLine()).decode('utf8')
self.ui.console.append(txt)
def finished(self):
self.read_data()
self.ui.termbutton.setText('Done')
self.timer.stop()
self.return_value = self.process.exitCode()
2013-09-28 03:50:06 +08:00
def terminated(self, foo):
self.process.kill()
self.timer.stop()
self.ui.done(self.return_value)
2013-09-28 03:50:06 +08:00
def timeout(self):
now = time.time()
duration = int(now - self.start_time)
msg = 'Elapsed time: %d:%d' % (duration // 60, duration % 60)
2013-09-28 03:50:06 +08:00
self.ui.timelabel.setText(msg)
2013-09-19 02:42:18 +08:00
class MesonGui():
2013-09-19 03:05:32 +08:00
def __init__(self, build_dir):
uifile = os.path.join(priv_dir, 'mesonmain.ui')
2013-09-19 02:42:18 +08:00
self.ui = uic.loadUi(uifile)
2013-09-19 03:05:32 +08:00
self.coredata_file = os.path.join(build_dir, 'meson-private/coredata.dat')
2013-09-28 00:38:09 +08:00
self.build_file = os.path.join(build_dir, 'meson-private/build.dat')
2013-09-19 03:05:32 +08:00
if not os.path.exists(self.coredata_file):
2013-10-01 02:53:47 +08:00
print("Argument is not build directory.")
2013-09-19 03:05:32 +08:00
sys.exit(1)
self.coredata = pickle.load(open(self.coredata_file, 'rb'))
2013-09-28 00:38:09 +08:00
self.build = pickle.load(open(self.build_file, 'rb'))
2013-09-28 01:44:31 +08:00
self.build_dir = self.build.environment.build_dir
self.src_dir = self.build.environment.source_dir
2013-09-29 23:17:07 +08:00
self.build_models()
2013-10-19 01:55:10 +08:00
self.options = OptionForm(self.coredata, self.ui.option_form)
2013-09-29 23:17:07 +08:00
self.ui.show()
def build_models(self):
2013-09-19 04:01:07 +08:00
self.path_model = PathModel(self.coredata)
2013-09-28 00:38:09 +08:00
self.target_model = TargetModel(self.build)
2013-09-28 01:44:31 +08:00
self.dep_model = DependencyModel(self.coredata)
2013-09-29 22:02:53 +08:00
self.core_model = CoreModel(self.coredata)
2013-09-19 03:05:32 +08:00
self.fill_data()
2013-09-29 22:02:53 +08:00
self.ui.core_view.setModel(self.core_model)
hv = QHeaderView(1)
hv.setModel(self.core_model)
self.ui.core_view.setHeader(hv)
2013-09-19 03:43:33 +08:00
self.ui.path_view.setModel(self.path_model)
2013-09-28 01:01:41 +08:00
hv = QHeaderView(1)
hv.setModel(self.path_model)
self.ui.path_view.setHeader(hv)
2013-09-28 00:38:09 +08:00
self.ui.target_view.setModel(self.target_model)
2013-09-28 01:01:41 +08:00
hv = QHeaderView(1)
hv.setModel(self.target_model)
self.ui.target_view.setHeader(hv)
2013-09-28 01:44:31 +08:00
self.ui.dep_view.setModel(self.dep_model)
hv = QHeaderView(1)
hv.setModel(self.dep_model)
self.ui.dep_view.setHeader(hv)
2013-09-28 03:50:06 +08:00
self.ui.compile_button.clicked.connect(self.compile)
self.ui.test_button.clicked.connect(self.run_tests)
self.ui.install_button.clicked.connect(self.install)
2013-09-28 04:00:33 +08:00
self.ui.clean_button.clicked.connect(self.clean)
self.ui.save_button.clicked.connect(self.save)
2013-09-19 03:43:33 +08:00
2013-09-19 03:05:32 +08:00
def fill_data(self):
2013-09-28 01:03:22 +08:00
self.ui.project_label.setText(self.build.project)
2013-09-19 03:05:32 +08:00
self.ui.srcdir_label.setText(self.src_dir)
self.ui.builddir_label.setText(self.build_dir)
if self.coredata.cross_file is None:
btype = 'Native build'
else:
btype = 'Cross build'
self.ui.buildtype_label.setText(btype)
2013-09-19 02:42:18 +08:00
2013-09-28 03:50:06 +08:00
def run_process(self, cmdlist):
cmdlist = [shutil.which(environment.detect_ninja())] + cmdlist
dialog = ProcessRunner(self.build.environment.build_dir, cmdlist)
dialog.run()
2013-09-28 03:50:06 +08:00
def compile(self, foo):
self.run_process([])
def run_tests(self, foo):
self.run_process(['test'])
def install(self, foo):
self.run_process(['install'])
2013-09-28 04:00:33 +08:00
def clean(self, foo):
self.run_process(['clean'])
def save(self, foo):
pickle.dump(self.coredata, open(self.coredata_file, 'wb'))
2013-09-28 03:50:06 +08:00
2013-09-30 00:35:39 +08:00
class Starter():
def __init__(self, sdir):
uifile = os.path.join(priv_dir, 'mesonstart.ui')
2013-09-30 00:35:39 +08:00
self.ui = uic.loadUi(uifile)
self.ui.source_entry.setText(sdir)
self.dialog = PyQt5.QtWidgets.QFileDialog()
if len(sdir) == 0:
self.dialog.setDirectory(os.getcwd())
else:
self.dialog.setDirectory(sdir)
self.ui.source_browse_button.clicked.connect(self.src_browse_clicked)
self.ui.build_browse_button.clicked.connect(self.build_browse_clicked)
self.ui.cross_browse_button.clicked.connect(self.cross_browse_clicked)
2013-09-30 00:47:49 +08:00
self.ui.source_entry.textChanged.connect(self.update_button)
self.ui.build_entry.textChanged.connect(self.update_button)
self.ui.generate_button.clicked.connect(self.generate)
self.update_button()
self.ui.show()
def generate(self):
srcdir = self.ui.source_entry.text()
builddir = self.ui.build_entry.text()
cross = self.ui.cross_entry.text()
cmdlist = [os.path.join(os.path.split(__file__)[0], 'meson.py'), srcdir, builddir]
if cross != '':
cmdlist += ['--cross', cross]
pr = ProcessRunner(os.getcwd(), cmdlist)
rvalue = pr.run()
if rvalue == 0:
os.execl(__file__, 'dummy', builddir)
2013-09-30 00:47:49 +08:00
def update_button(self):
if self.ui.source_entry.text() == '' or self.ui.build_entry.text() == '':
self.ui.generate_button.setEnabled(False)
else:
self.ui.generate_button.setEnabled(True)
2013-09-30 00:35:39 +08:00
def src_browse_clicked(self):
self.dialog.setFileMode(2)
if self.dialog.exec():
self.ui.source_entry.setText(self.dialog.selectedFiles()[0])
def build_browse_clicked(self):
self.dialog.setFileMode(2)
if self.dialog.exec():
self.ui.build_entry.setText(self.dialog.selectedFiles()[0])
def cross_browse_clicked(self):
self.dialog.setFileMode(1)
if self.dialog.exec():
self.ui.cross_entry.setText(self.dialog.selectedFiles()[0])
2013-09-19 02:42:18 +08:00
if __name__ == '__main__':
app = QApplication(sys.argv)
2013-09-30 00:35:39 +08:00
if len(sys.argv) == 1:
arg = ""
elif len(sys.argv) == 2:
arg = sys.argv[1]
else:
print(sys.argv[0], "<build or source dir>")
2013-09-19 02:42:18 +08:00
sys.exit(1)
2013-09-30 00:35:39 +08:00
if os.path.exists(os.path.join(arg, 'meson-private/coredata.dat')):
gui = MesonGui(arg)
else:
runner = Starter(arg)
2013-09-19 02:42:18 +08:00
sys.exit(app.exec_())