Skip to content

Commit

Permalink
Reimplement tools in python
Browse files Browse the repository at this point in the history
  • Loading branch information
robertu94 committed Aug 12, 2019
1 parent 47227dc commit 1a24dbe
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 16 deletions.
15 changes: 14 additions & 1 deletion m/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,37 @@
from .plugins.Base import MBuildTool

import argparse
import itertools
import shlex

def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--cmdline", "-c", action="append", default=list())
parser.set_defaults(action=lambda m: m.build())

sp = parser.add_subparsers()

test = sp.add_parser("test", aliases=["t"])
test.set_defaults(action=lambda m: m.test())

clean = sp.add_parser("clean", aliases=["c"])
clean.set_defaults(action=lambda m: m.clean())

build = sp.add_parser("build", aliases=['b'])
build.set_defaults(action=lambda m: m.build())

install = sp.add_parser("install", aliases=['i'])
install.set_defaults(action=lambda m: m.install())

build = sp.add_parser("settings", aliases=['s'])
build.set_defaults(action=lambda m: m.settings())

return parser.parse_args()

def main():
args = parse_args()
tool = MBuildTool()
args.cmdline = list(itertools.chain(*[shlex.split(arg) for arg in args.cmdline]))
tool = MBuildTool(args)
args.action(tool)


Expand Down
52 changes: 52 additions & 0 deletions m/plugins/Autotools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from multiprocessing import cpu_count
from subprocess import run
from .Base import plugin, BasePlugin, PluginSupport

@plugin
class AutotoolsPlugin(BasePlugin):

def build(self, settings):
"""compiles the source code or a subset thereof"""

if not (settings['repo_base'].value / "configure").exists() and (settings['repo_base'].value / "autogen.sh").exists():
run(["./autogen.sh"], cwd=settings['repo_base'].value)

if not (settings['repo_base'].value / "configure").exists() and (settings['repo_base'].value / "configure").exists():
run(["./configure"], cwd=settings['repo_base'].value)

run(["make", "-j", str(cpu_count())], cwd=settings['repo_base'].value)

def test(self, settings):
"""runs automated tests on source code or a subset there of"""
run(["make", "-j", str(cpu_count()), "check"], cwd=settings['repo_base'].value)

def clean(self, settings):
"""cleans source code or a subset there of"""
run(["make", "-j", str(cpu_count()), "clean"], cwd=settings['repo_base'].value)

def install(self, settings):
"""cleans source code or a subset there of"""
run(["make", "-j", str(cpu_count()), "install"], cwd=settings['repo_base'].value)


@staticmethod
def _supported(settings):
"""returns a dictionary of supported functions"""
if 'repo_base' in settings and (
(settings['repo_base'].value / "autogen.sh").exists() or
(settings['repo_base'].value / "configure").exists() or
(settings['repo_base'].value / "Makefile").exists() or
(settings['repo_base'].value / "makefile").exists()
):
state = PluginSupport.DEFAULT_MAIN
else:
state = PluginSupport.NOT_ENABLED_BY_REPOSITORY


return {
"build": state,
"test": state,
"clean": state,
"install": state
}

45 changes: 35 additions & 10 deletions m/plugins/Base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from collections import defaultdict
from pathlib import Path
import itertools
import enum
import pprint
import typing
import logging

logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger(__name__)

def get_class_name(cls):
Expand Down Expand Up @@ -40,7 +41,7 @@ def __repr__(self):
return str(self)


class PluginSupport(enum.Enum):
class PluginSupport(enum.IntEnum):
"""
Modes for supporting functions
Expand Down Expand Up @@ -104,6 +105,11 @@ def clean(self, settings):
"""cleans source code or a subset there of"""
raise NotProvidedError("clean", self)

def install(self, settings):
"""cleans source code or a subset there of"""
raise NotProvidedError("install", self)


def get_settings_factory(self, cls=None, priority=Setting.DEFAULT):
"""returns a helper function that fills in commmon arguments on the Setting object"""
if cls is None:
Expand Down Expand Up @@ -149,16 +155,30 @@ def plugin(cls):

class MBuildTool:

def __init__(self):
self._settings = {}
def __init__(self, args):
self._settings = self._args_to_settings(args)
self._actions_run = 0

def _args_to_settings(self, args):
return {
key:Setting(key, value, "CmdlineArguments", priority=Setting.URGENT)
for key, value
in vars(args).items()
if any(isinstance(value, cls) for cls in (int,str,list,Path))
}

def _active_plugin_loglevel(self, status):
if status >= PluginSupport.DEFAULT_BEFORE_MAIN and self._actions_run > 0:
return logging.INFO
else:
return logging.DEBUG

def _find_active_plugins(self, method: str):
"""finds the plugin that should conduct the given call"""
active_plugins = defaultdict(list)
for plugin in ALL_PLUGINS:
status = plugin.check(method, self._settings)
LOGGER.debug("plugin %s is %s for %s", get_class_name(plugin), status.name, method)
LOGGER.log(self._active_plugin_loglevel(status),"plugin %s is %s for %s", get_class_name(plugin), status.name, method)
if status in (PluginSupport.DEFAULT_BEFORE_MAIN, PluginSupport.REQUESTED_SETTINGS_BEFORE, PluginSupport.REQUESTED_CMD_BEFORE):
active_plugins['before'].append(plugin)
elif status in (PluginSupport.DEFAULT_MAIN, PluginSupport.REQUESTED_SETTINGS_MAIN, PluginSupport.REQUESTED_CMD_MAIN):
Expand All @@ -168,9 +188,6 @@ def _find_active_plugins(self, method: str):
return active_plugins


def _get_settings(self):
"""code to query all plug-ins for settings and combine them"""

def _run_action(self, method: str):
"""implmementation of the plugin calling logic"""
LOGGER.info("running %s", method)
Expand All @@ -182,16 +199,18 @@ def _run_action(self, method: str):
results.append(getattr(main_plugin, method)(self._settings))
for after_plugin in plugins['after']:
results.append(getattr(after_plugin, method)(self._settings))

self._actions_run += 1
if method == "settings":
self._update_settings(results)

def _update_settings(self, new_settings, inital=False):
def _update_settings(self, new_settings):
for new_setting in itertools.chain(*new_settings):
current_setting = self._settings.get(new_setting.name, None)
if current_setting is not None:
if new_setting.priority > current_setting.priority:
self._settings[current_setting.name] = new_setting
elif new_setting.priority == current_setting.priority and inital:
elif new_setting.priority == current_setting.priority and self._actions_run > 0:
LOGGER.debug("ignoring duplicate priority setting %s from %s and %s",
new_setting.name,
current_setting.source,
Expand All @@ -215,6 +234,12 @@ def clean(self):
self._run_action("settings")
self._run_action("clean")

def install(self):
"""delegates to the right clean function"""
self._run_action("settings")
self._run_action("install")


def settings(self):
"""delegates to the right clean function"""
self._run_action("settings")
Expand Down
53 changes: 48 additions & 5 deletions m/plugins/CMake.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,64 @@
@plugin
class CMakePlugin(BasePlugin):

def configure(self, settings):
"""configure the build directory"""
if not self.is_configured(settings):
settings['build_dir'].value.mkdir(exist_ok=True)
run(["cmake","..","-G","Ninja"], cwd=settings['build_dir'].value)

def is_configured(self, settings):
"""test if the build directory is configured"""
return settings['build_dir'].value.exists() and (
(settings['build_dir'].value / "build.ninja").exists() or
(settings['build_dir'].value / "Makefile").exists()
)

def build(self, settings):
"""compiles the source code or a subset thereof"""
run(["cmake",".."], cwd=settings['build_dir'].value)
self.configure(settings)

if self.is_configured(settings):
print("m[1]: Entering directory", str(settings['build_dir'].value))
run(["cmake","--build",".", *settings['cmdline'].value], cwd=settings['build_dir'].value)
else:
print("failed to configure")

def test(self, settings):
"""runs automated tests on source code or a subset there of"""
raise NotProvidedError("test", self)
self.configure(settings)

if self.is_configured(settings):
print("m[1]: Entering directory", str(settings['build_dir'].value))
run(["ctest", *settings['cmdline'].value], cwd=settings['build_dir'].value)
else:
print("failed to configure")

def clean(self, settings):
"""cleans source code or a subset there of"""
raise NotProvidedError("clean", self)
self.configure(settings)

if self.is_configured(settings):
print("m[1]: Entering directory", str(settings['build_dir'].value))
run(["cmake", "--build", ".", "--target", "clean"], cwd=settings['build_dir'].value)
else:
print("failed to configure")

def install(self, settings):
"""compiles the source code or a subset thereof"""
self.configure(settings)

if self.is_configured(settings):
print("m[1]: Entering directory", str(settings['build_dir'].value))
run(["cmake","--install","."], cwd=settings['build_dir'].value)
else:
print("failed to configure")


@staticmethod
def _supported(settings):
"""returns a dictionary of supported functions"""
if 'repo_dir' in settings and (settings['repo_dir'].value / "CMakeLists.txt").exists():
if 'repo_base' in settings and (settings['repo_base'].value / "CMakeLists.txt").exists():
state = PluginSupport.DEFAULT_MAIN
else:
state = PluginSupport.NOT_ENABLED_BY_REPOSITORY
Expand All @@ -28,6 +70,7 @@ def _supported(settings):
return {
"build": state,
"test": state,
"clean": state
"clean": state,
"install": state
}

75 changes: 75 additions & 0 deletions m/plugins/Meson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from subprocess import run
from .Base import plugin, BasePlugin, PluginSupport

@plugin
class MesonPlugin(BasePlugin):

def configure(self, settings):
"""configure the build directory"""
if not self.is_configured(settings):
settings['build_dir'].value.mkdir(exist_ok=True)
run(["meson", str(settings['build_dir'].value)], cwd=settings['repo_base'].value)

def is_configured(self, settings):
"""test if the build directory is configured"""
return settings['build_dir'].value.exists() and (
(settings['build_dir'].value / "build.ninja").exists()
)

def build(self, settings):
"""compiles the source code or a subset thereof"""
self.configure(settings)

if self.is_configured(settings):
print("m[1]: Entering directory", str(settings['build_dir'].value))
run(["ninja","-C", str(settings['build_dir'].value), *settings['cmdline'].value], cwd=settings['repo_base'].value)
else:
print("failed to configure")

def test(self, settings):
"""runs automated tests on source code or a subset there of"""
self.configure(settings)

if self.is_configured(settings):
print("m[1]: Entering directory", str(settings['build_dir'].value))
run(["meson", "test", *settings['cmdline'].value], cwd=settings['build_dir'].value)
else:
print("failed to configure")

def clean(self, settings):
"""cleans source code or a subset there of"""
self.configure(settings)

if self.is_configured(settings):
print("m[1]: Entering directory", str(settings['build_dir'].value))
run(["ninja", "clean"], cwd=settings['build_dir'].value)
else:
print("failed to configure")

def install(self, settings):
"""cleans source code or a subset there of"""
self.configure(settings)

if self.is_configured(settings):
print("m[1]: Entering directory", str(settings['build_dir'].value))
run(["ninja", "install"], cwd=settings['build_dir'].value)
else:
print("failed to configure")


@staticmethod
def _supported(settings):
"""returns a dictionary of supported functions"""
if 'repo_base' in settings and (settings['repo_base'].value / "meson.build").exists():
state = PluginSupport.DEFAULT_MAIN
else:
state = PluginSupport.NOT_ENABLED_BY_REPOSITORY


return {
"build": state,
"test": state,
"clean": state,
"install": state
}

0 comments on commit 1a24dbe

Please sign in to comment.