Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions test/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,58 @@ def test_unsupported_target(self, linux):
build(tree=linux, targets=["unknown-target"])


class TestMakeTarget:
def test_adds_make_target(self, linux):
b = Build(tree=linux, targets=[], make_target=["drivers/mmc/"])
names = [t.name for t in b.targets]
assert "make:drivers/mmc/" in names

def test_pulls_in_config(self, linux):
b = Build(tree=linux, targets=[], make_target=["drivers/mmc/"])
names = [t.name for t in b.targets]
assert "config" in names

def test_does_not_pull_defaults(self, linux):
b = Build(tree=linux, targets=[], make_target=["drivers/mmc/"])
names = [t.name for t in b.targets]
assert "default" not in names
assert "kernel" not in names

def test_positional_make_target(self, linux):
b = Build(tree=linux, targets=["config", "drivers/dma/"])
names = [t.name for t in b.targets]
assert "make:drivers/dma/" in names
assert "config" in names

def test_positional_object_target(self, linux):
b = Build(tree=linux, targets=["config", "kernel/livepatch/patch.o"])
names = [t.name for t in b.targets]
assert "make:kernel/livepatch/patch.o" in names

def test_unknown_bareword_still_fails(self, linux):
with pytest.raises(tuxmake.exceptions.UnsupportedTarget):
Build(tree=linux, targets=["typo-target"])

def test_multiple_make_targets(self, linux):
b = Build(
tree=linux,
targets=[],
make_target=["drivers/mmc/", "kernel/livepatch/patch.o"],
)
names = [t.name for t in b.targets]
assert "make:drivers/mmc/" in names
assert "make:kernel/livepatch/patch.o" in names

def test_no_duplicate_when_positional_and_flag(self, linux):
b = Build(
tree=linux,
targets=["drivers/mmc/"],
make_target=["drivers/mmc/"],
)
names = [t.name for t in b.targets]
assert names.count("make:drivers/mmc/") == 1


class TestKconfig:
def test_kconfig_default(self, linux, Popen):
b = Build(tree=linux, targets=["config"])
Expand Down
23 changes: 23 additions & 0 deletions test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,29 @@ def test_config_multiple(self, builder):
tuxmake("config", "kernel")
assert args(builder).targets == ["config", "kernel"]

def test_make_target(self, builder):
tuxmake("--make-target=drivers/mmc/")
assert args(builder).make_target == ["drivers/mmc/"]

def test_make_target_with_positional(self, builder):
tuxmake("config", "--make-target=drivers/mmc/")
assert args(builder).targets == ["config"]
assert args(builder).make_target == ["drivers/mmc/"]

def test_positional_kconfig(self, builder):
tuxmake("defconfig")
assert args(builder).kconfig == "defconfig"
assert not getattr(args(builder), "targets", [])

def test_positional_kconfig_allmodconfig(self, builder):
tuxmake("allmodconfig")
assert args(builder).kconfig == "allmodconfig"

def test_explicit_kconfig_wins_over_positional(self, builder):
tuxmake("--kconfig=tinyconfig", "defconfig")
assert args(builder).kconfig == "tinyconfig"
assert "defconfig" not in getattr(args(builder), "targets", [])


class TestMakeVariables:
def test_basic(self, builder):
Expand Down
6 changes: 6 additions & 0 deletions test/test_cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ def test_kconfig_add(self, cmdline):
assert "--kconfig-add=foo.config" in cmd
assert "--kconfig-add=bar.config" in cmd

def test_make_target(self, cmdline):
build = Build(targets=[], make_target=["drivers/mmc/"])
cmd = cmdline.reproduce(build)
assert "--make-target=drivers/mmc/" in cmd
assert "make:drivers/mmc/" not in cmd

def test_debug(self, cmdline):
cmd = cmdline.reproduce(Build(debug=True))
assert "--debug" in cmd
Expand Down
24 changes: 23 additions & 1 deletion test/test_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

import tuxmake.exceptions
from tuxmake.arch import Native
from tuxmake.target import Compression, Target, Config, Kselftest, create_target
from tuxmake.target import (
Compression,
Target,
Config,
Kselftest,
MakeTarget,
create_target,
)


@pytest.fixture
Expand Down Expand Up @@ -231,6 +238,21 @@ def test_kdir_set_to_build_dir(self, build):
assert kselftest.makevars.get("KDIR") == "{build_dir}"


class TestMakeTarget:
@pytest.fixture
def make_target(self, build):
return MakeTarget("drivers/mmc/", build)

def test_name(self, make_target):
assert make_target.name == "make:drivers/mmc/"

def test_commands(self, make_target):
assert make_target.commands[0] == ["{make}", "drivers/mmc/"]

def test_depends_on_config(self, make_target):
assert make_target.dependencies == ["config"]


class TestCompression:
def test_invalid_compression(self):
with pytest.raises(tuxmake.exceptions.UnsupportedCompression):
Expand Down
19 changes: 15 additions & 4 deletions tuxmake/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from tuxmake.output import get_new_output_dir, get_default_korg_toolchains_dir
from tuxmake.target import Compression
from tuxmake.target import default_compression
from tuxmake.target import MakeTarget
from tuxmake.target import create_target
from tuxmake.runtime import Runtime, DockerRuntime
from tuxmake.runtime import Terminated
Expand Down Expand Up @@ -181,6 +182,7 @@ def __init__(
targets=defaults.targets,
compression_type=None,
kernel_image=None,
make_target=None,
jobs=None,
runtime=None,
fail_fast=False,
Expand Down Expand Up @@ -227,7 +229,7 @@ def __init__(
self.dynamic_make_variables = dict(self.target_arch.dynamic_makevars)

if not targets:
targets = defaults.targets
targets = [] if make_target else defaults.targets

if kernel_image:
self.target_overrides = {"kernel": kernel_image}
Expand All @@ -242,6 +244,15 @@ def __init__(
self.__ordering_only_targets__ = {}
for t in targets:
self.add_target(t)
self.make_target = make_target or []
if self.make_target:
self.add_target("config")
existing = {t.name for t in self.targets}
for mt in self.make_target:
target = MakeTarget(mt, self, self.compression)
if target.name not in existing:
self.__ordering_only_targets__[target.name] = False
self.targets.append(target)
self.cleanup_targets()
self.extend_kconfig()

Expand Down Expand Up @@ -301,10 +312,10 @@ def add_target(self, target_name, ordering_only=False):
target = create_target(target_name, self, self.compression)

if ordering_only:
if target_name not in self.__ordering_only_targets__:
self.__ordering_only_targets__[target_name] = True
if target.name not in self.__ordering_only_targets__:
self.__ordering_only_targets__[target.name] = True
else:
self.__ordering_only_targets__[target_name] = False
self.__ordering_only_targets__[target.name] = False

for d in target.dependencies:
self.add_target(d, ordering_only=ordering_only)
Expand Down
9 changes: 9 additions & 0 deletions tuxmake/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import timedelta
import os
import pathlib
import re
import subprocess
import shlex
import sys
Expand Down Expand Up @@ -141,6 +142,14 @@ def format_yes_no(b, length):
options.make_variables = dict((arg.split("=") for arg in key_values))
options.targets = [arg for arg in options.targets if "=" not in arg]

kconfig_re = re.compile(r"^[\w\-]+config$")
for arg in list(options.targets):
if kconfig_re.match(arg):
if not options.kconfig:
options.kconfig = arg
options.targets.remove(arg)
break

build_args = {
k: v
for k, v in options.__dict__.items()
Expand Down
10 changes: 10 additions & 0 deletions tuxmake/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ def build_parser(cls=argparse.ArgumentParser, **kwargs):
type=str,
help="Kernel image to build, overriding the default image name for the target architecture.",
)
target.add_argument(
"-M",
"--make-target",
type=str,
action="append",
default=[],
help="Arbitrary Kbuild target to pass to make, e.g. drivers/mmc/ or kernel/livepatch/patch.o. Can be specified multiple times. Kbuild-like positional targets (with / or .o/.ko extensions) also work without this flag.",
)

buildenv = parser.add_argument_group("Build environment options")
buildenv.add_argument(
Expand Down Expand Up @@ -329,6 +337,8 @@ def reproduce(self, build):
for k, v in build.make_variables.items():
cmd.append(f"{k}={v}")
for target in build.targets:
if target.name.startswith("make:"):
continue
cmd.append(target.name)

return cmd
Expand Down
27 changes: 27 additions & 0 deletions tuxmake/target.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import List, Tuple

from configparser import ConfigParser
from pathlib import Path
import re
import shlex
Expand Down Expand Up @@ -279,6 +280,23 @@ def __init_config__(self):
self.artifacts["vmlinux"] = "vmlinux"


class MakeTarget(Target):
"""Pass-through target that runs `make <make_target>`."""

def __init__(self, make_target, build, compression=default_compression):
self.build = build
self.compression = compression
self.target_arch = build.target_arch
self.name = f"make:{make_target}"
self.config = ConfigParser()
self.config["target"] = {
"description": f"make {make_target}",
"dependencies": "config",
"commands": shlex.quote("{make}") + " " + shlex.quote(make_target),
}
self.__init_config__()


class Kselftest(Target):
def __init_config__(self):
super().__init_config__()
Expand All @@ -303,6 +321,15 @@ def _is_clang_toolchain(self):
}


_make_target_re = re.compile(r"/|\.(o|ko|s|i|lst|dtb|dtbo)$")


def create_target(name, build, compression=default_compression):
cls = __special_targets__.get(name, Target)
# Fall back to a pass-through make target when the name looks like
# a Kbuild target (contains / or ends in .o / .ko / .s / .i / ...).
# Lets users pass drivers/dma/ or kernel/livepatch/patch.o as
# positional arguments without --make-target=.
if cls is Target and _make_target_re.search(name):
return MakeTarget(name, build, compression)
return cls(name, build, compression)
Loading