refactor(./make): better way to discover deps (#338)

Cleanup after https://github.com/ooni/probe/issues/1466
This commit is contained in:
Simone Basso 2021-05-07 12:11:08 +02:00 committed by GitHub
parent e753e57da3
commit 2c0cef4b1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

570
make
View File

@ -7,7 +7,6 @@ from __future__ import annotations
import datetime
import getopt
import json
import os
import platform
import shlex
@ -150,11 +149,11 @@ class ConfigFromCLI:
"""ConfigFromCLI parses options from CLI flags."""
@classmethod
def parse(cls, targets: List[str]) -> ConfigFromCLI:
def parse(cls, targets: List[str], top_targets: List[Target]) -> ConfigFromCLI:
"""parse parses command line options and returns a
suitable configuration object."""
conf = cls()
conf._parse(targets)
conf._parse(targets, top_targets)
return conf
def __init__(self) -> None:
@ -198,8 +197,8 @@ embedding a psiphon config file into the generated binary; you should
use this option when you cannot clone the private repository containing
the psiphon configuration file.
The second form of the command lists all the available targets as
a pretty-printed JSON list.
The second form of the command lists all the available targets, showing
top-level targets and their recursive dependencies.
The third form of the command prints this help screen.
"""
@ -211,7 +210,7 @@ The third form of the command prints this help screen.
sys.stderr.write(cls._usage_string)
sys.exit(exitcode)
def _parse(self, targets: List[str]):
def _parse(self, targets: List[str], top_targets: List[Target]):
try:
opts, args = getopt.getopt(
sys.argv[1:], "hlnt:vx", ["disable-embedding-psiphon-config", "help"]
@ -227,8 +226,7 @@ The third form of the command prints this help screen.
if key in ("-h", "--help"):
self._usage()
if key == "-l":
sys.stdout.write("{}\n".format(json.dumps(sorted(targets), indent=4)))
sys.exit(0)
self._list_targets(top_targets)
if key == "-n":
self._dry_run = True
continue
@ -251,6 +249,22 @@ The third form of the command prints this help screen.
sys.stderr.write("try `./make -l` to see the available targets.\n")
sys.exit(1)
def _list_targets(self, top_targets: List[Target]) -> NoReturn:
for tgt in top_targets:
self._print_target(tgt, 0)
sys.exit(0)
def _print_target(self, target: Target, indent: int) -> None:
sys.stdout.write(
'{}{}{}\n'.format(
" " * indent, target.name(), ":" if target.deps() else ""
)
)
for dep in target.deps():
self._print_target(dep, indent + 1)
if indent <= 0:
sys.stdout.write("\n")
class Engine(Protocol):
"""Engine is an engine for building targets."""
@ -428,7 +442,7 @@ class Environ:
variables are set. They will be restored to their previous
value when we are leaving the context."""
def __init__(self, engine: Engine, key: str, value: str):
def __init__(self, engine: Engine, key: str, value: str) -> None:
self._engine = engine
self._key = key
self._value = value
@ -463,65 +477,93 @@ class Target(Protocol):
def build(self, engine: Engine, options: Options) -> None:
"""build builds the specified target."""
def deps(self) -> List[Target]:
"""deps returns the target dependencies."""
class SDKGolangGo:
class BaseTarget:
"""BaseTarget is the base class of all targets."""
def __init__(self, name: str, deps: List[Target]) -> None:
# prevent child classes from easily using these variables
self.__name = name
self.__deps = deps
def name(self) -> str:
"""name implements Target.name"""
return self.__name
def build_child_targets(self, engine: Engine, options: Options) -> None:
"""build_child_targets builds all the child targets"""
for dep in self.__deps:
dep.build(engine, options)
def deps(self) -> List[Target]:
"""deps implements Target.deps."""
return self.__deps
class SDKGolangGo(BaseTarget):
"""SDKGolangGo creates ${cachedir}/SDK/golang."""
# We download a golang SDK from upstream to make sure we
# are always using a specific version of golang/go.
_name = os.path.join(cachedir(), "SDK", "golang")
def name(self) -> str:
return self._name
def __init__(self) -> None:
name = os.path.join(cachedir(), "SDK", "golang")
super().__init__(name, [])
def binpath(self) -> str:
return os.path.join(self._name, "go", "bin")
"""binpath returns the path where the go binary is installed."""
return os.path.join(self.name(), "go", "bin")
def build(self, engine: Engine, options: Options) -> None:
if os.path.isdir(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isdir(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
log("\n./make: building {}...".format(self._name))
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
engine.require("mkdir", "curl", "shasum", "rm", "tar", "echo")
filename = "go{}.{}-{}.tar.gz".format(goversion(), goos(), goarch())
url = "https://golang.org/dl/{}".format(filename)
engine.run(["mkdir", "-p", self._name])
filepath = os.path.join(self._name, filename)
engine.run(["mkdir", "-p", self.name()])
filepath = os.path.join(self.name(), filename)
engine.run(["curl", "-fsSLo", filepath, url])
sha256file = os.path.join(cachedir(), "SDK", "SHA256")
engine.echo_to_file("{} {}".format(gosha256sum(), filepath), sha256file)
engine.run(["shasum", "--check", sha256file])
engine.run(["rm", sha256file])
engine.run(["tar", "-xf", filename], cwd=self._name)
engine.run(["tar", "-xf", filename], cwd=self.name())
engine.run(["rm", filepath])
def goroot(self):
"""goroot returns the goroot."""
return os.path.join(self._name, "go")
return os.path.join(self.name(), "go")
class SDKOONIGo:
class SDKOONIGo(BaseTarget):
"""SDKOONIGo creates ${cachedir}/SDK/oonigo."""
# We use a private fork of golang/go on Android as a
# workaround for https://github.com/ooni/probe/issues/1444
_name = os.path.join(cachedir(), "SDK", "oonigo")
def name(self) -> str:
return self._name
def __init__(self) -> None:
name = os.path.join(cachedir(), "SDK", "oonigo")
self._gogo = SDKGolangGo()
super().__init__(name, [self._gogo])
def binpath(self) -> str:
return os.path.join(self._name, "bin")
"""binpath returns the path where the go binary is installed."""
return os.path.join(self.name(), "bin")
def build(self, engine: Engine, options: Options) -> None:
if os.path.isdir(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isdir(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
golang_go = SDKGolangGo()
golang_go.build(engine, options)
log("\n./make: building {}...".format(self._name))
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
engine.require("git", "bash")
engine.run(
[
@ -533,42 +575,44 @@ class SDKOONIGo:
"--depth",
"8",
"https://github.com/ooni/go",
self._name,
self.name(),
]
)
with Environ(engine, "GOROOT_BOOTSTRAP", golang_go.goroot()):
with Environ(engine, "GOROOT_BOOTSTRAP", self._gogo.goroot()):
engine.run(
["./make.bash"],
cwd=os.path.join(self._name, "src"),
cwd=os.path.join(self.name(), "src"),
)
class SDKAndroid:
class SDKAndroid(BaseTarget):
"""SDKAndroid creates ${cachedir}/SDK/android."""
_name = os.path.join(cachedir(), "SDK", "android")
def name(self) -> str:
return self._name
def __init__(self) -> None:
name = os.path.join(cachedir(), "SDK", "android")
super().__init__(name, [])
def home(self) -> str:
return self._name
"""home returns the ANDROID_HOME"""
return self.name()
def ndk_home(self) -> str:
"""ndk_home returns the ANDROID_NDK_HOME"""
return os.path.join(self.home(), "ndk", android_ndk_version())
def build(self, engine: Engine, options: Options) -> None:
if os.path.isdir(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isdir(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
log("\n./make: building {}...".format(self._name))
log("\n./make: building {}...".format(self.name()))
engine.require("mkdir", "curl", "echo", "shasum", "rm", "unzip", "mv", "java")
filename = "commandlinetools-{}-{}_latest.zip".format(
android_cmdlinetools_os(), android_cmdlinetools_version()
)
url = "https://dl.google.com/android/repository/{}".format(filename)
engine.run(["mkdir", "-p", self._name])
filepath = os.path.join(self._name, filename)
engine.run(["mkdir", "-p", self.name()])
filepath = os.path.join(self.name(), filename)
engine.run(["curl", "-fsSLo", filepath, url])
sha256file = os.path.join(cachedir(), "SDK", "SHA256")
engine.echo_to_file(
@ -576,21 +620,21 @@ class SDKAndroid:
)
engine.run(["shasum", "--check", sha256file])
engine.run(["rm", sha256file])
engine.run(["unzip", filename], cwd=self._name)
engine.run(["unzip", filename], cwd=self.name())
engine.run(["rm", filepath])
# See https://stackoverflow.com/a/61176718 to understand why
# we need to reorganize the directories like this:
engine.run(
["mv", "cmdline-tools", android_cmdlinetools_version()], cwd=self._name
["mv", "cmdline-tools", android_cmdlinetools_version()], cwd=self.name()
)
engine.run(["mkdir", "cmdline-tools"], cwd=self._name)
engine.run(["mkdir", "cmdline-tools"], cwd=self.name())
engine.run(
["mv", android_cmdlinetools_version(), "cmdline-tools"], cwd=self._name
["mv", android_cmdlinetools_version(), "cmdline-tools"], cwd=self.name()
)
engine.run(
sdkmanager_install_cmd(
os.path.join(
self._name,
self.name(),
"cmdline-tools",
android_cmdlinetools_version(),
"bin",
@ -600,16 +644,15 @@ class SDKAndroid:
)
class OONIProbePrivate:
class OONIProbePrivate(BaseTarget):
"""OONIProbePrivate creates ${cachedir}/github.com/ooni/probe-private."""
# We use this private repository to copy the psiphon configuration
# file to embed into the ooniprobe binaries
_name = os.path.join(cachedir(), "github.com", "ooni", "probe-private")
def name(self) -> str:
return self._name
def __init__(self) -> None:
name = os.path.join(cachedir(), "github.com", "ooni", "probe-private")
super().__init__(name, [])
def copyfiles(self, engine: Engine, options: Options) -> None:
"""copyfiles copies psiphon config to the repository."""
@ -619,67 +662,66 @@ class OONIProbePrivate:
engine.run(
[
"cp",
os.path.join(self._name, "psiphon-config.json.age"),
os.path.join(self.name(), "psiphon-config.json.age"),
os.path.join("internal", "engine"),
]
)
engine.run(
[
"cp",
os.path.join(self._name, "psiphon-config.key"),
os.path.join(self.name(), "psiphon-config.key"),
os.path.join("internal", "engine"),
]
)
def build(self, engine: Engine, options: Options) -> None:
if os.path.isdir(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
if os.path.isdir(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
if options.disable_embedding_psiphon_config():
log("\n./make: {}: disabled by command line flags".format(self._name))
log("\n./make: {}: disabled by command line flags".format(self.name()))
return
log("\n./make: building {}...".format(self._name))
log("\n./make: building {}...".format(self.name()))
engine.require("git", "cp")
engine.run(
[
"git",
"clone",
"git@github.com:ooni/probe-private",
self._name,
self.name(),
]
)
class OONIMKAllAAR:
class OONIMKAllAAR(BaseTarget):
"""OONIMKAllAAR creates ./MOBILE/android/oonimkall.aar."""
_name = os.path.join(".", "MOBILE", "android", "oonimkall.aar")
def name(self) -> str:
return self._name
def __init__(self) -> None:
name = os.path.join(".", "MOBILE", "android", "oonimkall.aar")
self._ooprivate = OONIProbePrivate()
self._oonigo = SDKOONIGo()
self._android = SDKAndroid()
super().__init__(name, [self._ooprivate, self._oonigo, self._android])
def aarfile(self) -> str:
return self._name
"""aarfile returns the aar file path"""
return self.name()
def srcfile(self) -> str:
"""srcfile returns the path to the jar file containing sources."""
return os.path.join(".", "MOBILE", "android", "oonimkall-sources.jar")
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
ooprivate = OONIProbePrivate()
ooprivate.build(engine, options)
oonigo = SDKOONIGo()
oonigo.build(engine, options)
android = SDKAndroid()
android.build(engine, options)
log("\n./make: building {}...".format(self._name))
ooprivate.copyfiles(engine, options)
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
self._ooprivate.copyfiles(engine, options)
engine.require("sh", "javac")
self._go_get_gomobile(engine, options, oonigo)
self._gomobile_init(engine, oonigo, android)
self._gomobile_bind(engine, options, oonigo, android)
self._go_get_gomobile(engine, options, self._oonigo)
self._gomobile_init(engine, self._oonigo, self._android)
self._gomobile_bind(engine, options, self._oonigo, self._android)
# Implementation note: we use proxy scripts for go and gomobile
# that explicitly print what they resolve go and gomobile to using
@ -738,7 +780,7 @@ class OONIMKAllAAR:
cmdline.append("-target")
cmdline.append("android")
cmdline.append("-o")
cmdline.append(self._name)
cmdline.append(self.name())
if not options.disable_embedding_psiphon_config():
cmdline.append("-tags")
cmdline.append("ooni_psiphon_config")
@ -762,37 +804,37 @@ def sign(engine: Engine, filepath: str) -> str:
return filepath + ".asc"
class BundleJAR:
class BundleJAR(BaseTarget):
"""BundleJAR creates ./MOBILE/android/bundle.jar."""
# We upload the bundle.jar file to maven central to bless
# a new release of the OONI libraries for Android.
_name = os.path.join(".", "MOBILE", "android", "bundle.jar")
def name(self) -> str:
return self._name
def __init__(self) -> None:
name = os.path.join(".", "MOBILE", "android", "bundle.jar")
self._oonimkall = OONIMKAllAAR()
super().__init__(name, [self._oonimkall])
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
oonimkall = OONIMKAllAAR()
oonimkall.build(engine, options)
log("\n./make: building {}...".format(self._name))
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
engine.require("cp", "gpg", "jar")
version = datetime.datetime.now().strftime("%Y.%m.%d-%H%M%S")
engine.run(
[
"cp",
oonimkall.aarfile(),
self._oonimkall.aarfile(),
os.path.join("MOBILE", "android", "oonimkall-{}.aar".format(version)),
]
)
engine.run(
[
"cp",
oonimkall.srcfile(),
self._oonimkall.srcfile(),
os.path.join(
"MOBILE", "android", "oonimkall-{}-sources.jar".format(version)
),
@ -825,46 +867,41 @@ class BundleJAR:
)
class Phony:
class Phony(BaseTarget):
"""Phony is a phony target that executes one or more other targets."""
def __init__(self, name: str, depends: List[Target]):
self._name = name
self._depends = depends
def name(self) -> str:
return self._name
super().__init__(name, depends)
def build(self, engine: Engine, options: Options) -> None:
for dep in self._depends:
dep.build(engine, options)
"""build implements Target.build"""
self.build_child_targets(engine, options)
# Android is the top-level "android" target
ANDROID = Phony("android", [BundleJAR()])
class OONIMKAllFramework:
class OONIMKAllFramework(BaseTarget):
"""OONIMKAllFramework creates ./MOBILE/ios/oonimkall.framework."""
_name = os.path.join(".", "MOBILE", "ios", "oonimkall.framework")
def name(self) -> str:
return self._name
def __init__(self) -> None:
name = os.path.join(".", "MOBILE", "ios", "oonimkall.framework")
self._ooprivate = OONIProbePrivate()
self._gogo = SDKGolangGo()
super().__init__(name, [self._ooprivate, self._gogo])
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build."""
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
ooprivate = OONIProbePrivate()
ooprivate.build(engine, options)
gogo = SDKGolangGo()
gogo.build(engine, options)
log("\n./make: building {}...".format(self._name))
ooprivate.copyfiles(engine, options)
self._go_get_gomobile(engine, options, gogo)
self._gomobile_init(engine, gogo)
self._gomobile_bind(engine, options, gogo)
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
self._ooprivate.copyfiles(engine, options)
self._go_get_gomobile(engine, options, self._gogo)
self._gomobile_init(engine, self._gogo)
self._gomobile_bind(engine, options, self._gogo)
def _go_get_gomobile(
self,
@ -917,7 +954,7 @@ class OONIMKAllFramework:
cmdline.append("-target")
cmdline.append("ios")
cmdline.append("-o")
cmdline.append(self._name)
cmdline.append(self.name())
if not options.disable_embedding_psiphon_config():
cmdline.append("-tags")
cmdline.append("ooni_psiphon_config")
@ -930,22 +967,22 @@ class OONIMKAllFramework:
engine.run(cmdline)
class OONIMKAllFrameworkZip:
class OONIMKAllFrameworkZip(BaseTarget):
"""OONIMKAllFrameworkZip creates ./MOBILE/ios/oonimkall.framework.zip."""
_name = os.path.join(".", "MOBILE", "ios", "oonimkall.framework.zip")
def name(self) -> str:
return self._name
def __init__(self) -> None:
name = os.path.join(".", "MOBILE", "ios", "oonimkall.framework.zip")
ooframework = OONIMKAllFramework()
super().__init__(name, [ooframework])
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
engine.require("zip", "rm")
ooframework = OONIMKAllFramework()
ooframework.build(engine, options)
log("\n./make: building {}...".format(self._name))
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
engine.run(
[
"rm",
@ -965,17 +1002,17 @@ class OONIMKAllFrameworkZip:
)
class OONIMKAllPodspec:
class OONIMKAllPodspec(BaseTarget):
"""OONIMKAllPodspec creates ./MOBILE/ios/oonimkall.podspec."""
_name = os.path.join(".", "MOBILE", "ios", "oonimkall.podspec")
def name(self) -> str:
return self._name
def __init__(self) -> None:
name = os.path.join(".", "MOBILE", "ios", "oonimkall.podspec")
super().__init__(name, [])
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isfile(self.name()) and not options.dry_run():
log("./make: {}: already built".format(self.name()))
return
engine.require("cat", "sed")
release = engine.backticks(["git", "describe", "--tags"])
@ -983,7 +1020,7 @@ class OONIMKAllPodspec:
engine.cat_sed_redirect(
[("@VERSION@", version), ("@RELEASE@", release)],
os.path.join(".", "MOBILE", "template.podspec"),
self._name,
self.name(),
)
@ -991,31 +1028,29 @@ class OONIMKAllPodspec:
IOS = Phony("ios", [OONIMKAllFrameworkZip(), OONIMKAllPodspec()])
class MiniOONIDarwinOrWindows:
class MiniOONIDarwinOrWindows(BaseTarget):
def __init__(self, goos: str, goarch: str):
self._ext = ".exe" if goos == "windows" else ""
self._name = os.path.join(".", "CLI", goos, goarch, "miniooni" + self._ext)
self._os = goos
self._arch = goarch
def name(self) -> str:
return self._name
name = os.path.join(".", "CLI", goos, goarch, "miniooni" + self._ext)
self._ooprivate = OONIProbePrivate()
self._gogo = SDKGolangGo()
super().__init__(name, [self._ooprivate, self._gogo])
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
ooprivate = OONIProbePrivate()
ooprivate.build(engine, options)
gogo = SDKGolangGo()
gogo.build(engine, options)
log("\n./make: building {}...".format(self._name))
ooprivate.copyfiles(engine, options)
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
self._ooprivate.copyfiles(engine, options)
cmdline = [
"go",
"build",
"-o",
self._name,
self.name(),
"-ldflags=-s -w",
]
if options.debugging():
@ -1028,34 +1063,32 @@ class MiniOONIDarwinOrWindows:
with Environ(engine, "GOOS", self._os):
with Environ(engine, "GOARCH", self._arch):
with Environ(engine, "CGO_ENABLED", "0"):
with AugmentedPath(engine, gogo.binpath()):
with AugmentedPath(engine, self._gogo.binpath()):
engine.require("go")
engine.run(cmdline)
class MiniOONILinux:
class MiniOONILinux(BaseTarget):
def __init__(self, goarch: str):
self._name = os.path.join(".", "CLI", "linux", goarch, "miniooni")
self._arch = goarch
def name(self) -> str:
return self._name
name = os.path.join(".", "CLI", "linux", goarch, "miniooni")
self._ooprivate = OONIProbePrivate()
self._gogo = SDKGolangGo()
super().__init__(name, [self._ooprivate, self._gogo])
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
ooprivate = OONIProbePrivate()
ooprivate.build(engine, options)
gogo = SDKGolangGo()
gogo.build(engine, options)
log("\n./make: building {}...".format(self._name))
ooprivate.copyfiles(engine, options)
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
self._ooprivate.copyfiles(engine, options)
if self._arch == "arm":
with Environ(engine, "GOARM", "7"):
self._build(engine, options, gogo)
self._build(engine, options, self._gogo)
else:
self._build(engine, options, gogo)
self._build(engine, options, self._gogo)
def _build(self, engine: Engine, options: Options, gogo: SDKGolangGo) -> None:
cmdline = [
@ -1082,23 +1115,23 @@ class MiniOONILinux:
engine.run(cmdline)
# MINIOONI_TARGETS contains all miniooni targets
MINIOONI_TARGETS: List[Target] = [
MiniOONIDarwinOrWindows("darwin", "amd64"),
MiniOONIDarwinOrWindows("darwin", "arm64"),
MiniOONILinux("386"),
MiniOONILinux("amd64"),
MiniOONILinux("arm"),
MiniOONILinux("arm64"),
MiniOONIDarwinOrWindows("windows", "386"),
MiniOONIDarwinOrWindows("windows", "amd64"),
]
# MINIOONI is the top-level "miniooni" target.
MINIOONI = Phony("miniooni", MINIOONI_TARGETS)
MINIOONI = Phony(
"miniooni",
[
MiniOONIDarwinOrWindows("darwin", "amd64"),
MiniOONIDarwinOrWindows("darwin", "arm64"),
MiniOONILinux("386"),
MiniOONILinux("amd64"),
MiniOONILinux("arm"),
MiniOONILinux("arm64"),
MiniOONIDarwinOrWindows("windows", "386"),
MiniOONIDarwinOrWindows("windows", "amd64"),
],
)
class OONIProbeLinux:
class OONIProbeLinux(BaseTarget):
"""OONIProbeLinux builds ooniprobe for Linux."""
# TODO(bassosimone): this works out of the box on macOS and
@ -1106,20 +1139,19 @@ class OONIProbeLinux:
# is the right (set of) command(s) I should be checking for.
def __init__(self, goarch: str):
self._name = os.path.join(".", "CLI", "linux", goarch, "ooniprobe")
self._arch = goarch
def name(self) -> str:
return self._name
name = os.path.join(".", "CLI", "linux", goarch, "ooniprobe")
self._ooprivate = OONIProbePrivate()
super().__init__(name, [self._ooprivate])
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build."""
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
ooprivate = OONIProbePrivate()
ooprivate.build(engine, options)
log("\n./make: building {}...".format(self._name))
ooprivate.copyfiles(engine, options)
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
self._ooprivate.copyfiles(engine, options)
engine.require("docker")
# make sure we have the latest version of the container image
engine.run(
@ -1157,15 +1189,15 @@ class OONIProbeLinux:
engine.run(cmdline)
class OONIProbeWindows:
class OONIProbeWindows(BaseTarget):
"""OONIProbeWindows builds ooniprobe for Windows."""
def __init__(self, goarch: str):
self._name = os.path.join(".", "CLI", "windows", goarch, "ooniprobe.exe")
self._arch = goarch
def name(self) -> str:
return self._name
name = os.path.join(".", "CLI", "windows", goarch, "ooniprobe.exe")
self._ooprivate = OONIProbePrivate()
self._gogo = SDKGolangGo()
super().__init__(name, [self._ooprivate, self._gogo])
def _gcc(self) -> str:
if self._arch == "amd64":
@ -1175,20 +1207,18 @@ class OONIProbeWindows:
raise NotImplementedError
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
ooprivate = OONIProbePrivate()
ooprivate.build(engine, options)
gogo = SDKGolangGo()
gogo.build(engine, options)
log("\n./make: building {}...".format(self._name))
ooprivate.copyfiles(engine, options)
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
self._ooprivate.copyfiles(engine, options)
cmdline = [
"go",
"build",
"-o",
self._name,
self.name(),
"-ldflags=-s -w",
]
if options.debugging():
@ -1202,36 +1232,34 @@ class OONIProbeWindows:
with Environ(engine, "GOARCH", self._arch):
with Environ(engine, "CGO_ENABLED", "1"):
with Environ(engine, "CC", self._gcc()):
with AugmentedPath(engine, gogo.binpath()):
with AugmentedPath(engine, self._gogo.binpath()):
engine.require(self._gcc(), "go")
engine.run(cmdline)
class OONIProbeDarwin:
class OONIProbeDarwin(BaseTarget):
"""OONIProbeDarwin builds ooniprobe for macOS."""
def __init__(self, goarch: str):
self._name = os.path.join(".", "CLI", "darwin", goarch, "ooniprobe")
self._arch = goarch
def name(self) -> str:
return self._name
name = os.path.join(".", "CLI", "darwin", goarch, "ooniprobe")
self._ooprivate = OONIProbePrivate()
self._gogo = SDKGolangGo()
super().__init__(name, [self._ooprivate, self._gogo])
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self._name) and not options.dry_run():
log("\n./make: {}: already built".format(self._name))
"""build implements Target.build"""
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
ooprivate = OONIProbePrivate()
ooprivate.build(engine, options)
gogo = SDKGolangGo()
gogo.build(engine, options)
log("\n./make: building {}...".format(self._name))
ooprivate.copyfiles(engine, options)
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
self._ooprivate.copyfiles(engine, options)
cmdline = [
"go",
"build",
"-o",
self._name,
self.name(),
"-ldflags=-s -w",
]
if options.debugging():
@ -1244,24 +1272,22 @@ class OONIProbeDarwin:
with Environ(engine, "GOOS", "darwin"):
with Environ(engine, "GOARCH", self._arch):
with Environ(engine, "CGO_ENABLED", "1"):
with AugmentedPath(engine, gogo.binpath()):
with AugmentedPath(engine, self._gogo.binpath()):
engine.require("gcc", "go")
engine.run(cmdline)
class Debian:
"""Debian makes a debian package of a target artifact. It
currently only works with ooniprobe targets."""
class Debian(BaseTarget):
"""Debian makes a debian package for a given architecture."""
def __init__(self, arch: str, target: Target):
def __init__(self, arch: str):
name = "debian_{}".format(arch)
self._target = OONIProbeLinux(arch)
self._arch = arch
self._target = target
def name(self) -> str:
return "debian_{}".format(self._arch)
super().__init__(name, [self._target])
def build(self, engine: Engine, options: Options) -> None:
self._target.build(engine, options)
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
engine.require("docker")
# make sure we have the latest version of the container image
@ -1301,37 +1327,23 @@ class Debian:
engine.run(cmdline)
class Sign:
class Sign(BaseTarget):
"""Sign signs a specific target artefact."""
def __init__(self, target: Target):
self._target = target
def name(self) -> str:
return self._target.name() + ".asc"
name = self._target.name() + ".asc"
super().__init__(name, [self._target])
def build(self, engine: Engine, options: Options) -> None:
if os.path.isfile(self.name()) and not options.dry_run():
log("\n./make: {}: already built".format(self.name()))
return
self._target.build(engine, options)
self.build_child_targets(engine, options)
log("\n./make: building {}...".format(self.name()))
sign(engine, self._target.name())
# OONIPROBE_TARGETS contains all the ooniprobe targets
OONIPROBE_TARGETS: List[Target] = [
OONIProbeDarwin("amd64"),
OONIProbeDarwin("arm64"),
OONIProbeLinux("amd64"),
OONIProbeLinux("arm64"),
OONIProbeWindows("amd64"),
OONIProbeWindows("386"),
]
# OONIPROBE_SIGNED_TARGETS contains all the signed ooniprobe targets
OONIPROBE_SIGNED_TARGETS: List[Target] = [Sign(x) for x in OONIPROBE_TARGETS]
# OONIPROBE_RELEASE_DARWIN contains the release darwin targets
OONIPROBE_RELEASE_DARWIN = Phony(
"ooniprobe_release_darwin",
@ -1359,50 +1371,44 @@ OONIPROBE_RELEASE_WINDOWS = Phony(
],
)
# MOBILE_TARGETS contains the top-level mobile targets.
MOBILE_TARGETS: List[Target] = [
# DEBIAN is the top-level "debian" target.
DEBIAN = Phony(
"debian",
[
Debian("arm64"),
Debian("amd64"),
],
)
# TOP_TARGETS contains the top-level targets
TOP_TARGETS: List[Target] = [
ANDROID,
IOS,
]
# EXTRA_TARGETS contains extra top-level targets.
EXTRA_TARGETS: List[Target] = [
MINIOONI,
OONIMKAllAAR(),
OONIMKAllFrameworkZip(),
OONIPROBE_RELEASE_DARWIN,
OONIPROBE_RELEASE_LINUX,
OONIPROBE_RELEASE_WINDOWS,
DEBIAN,
]
# DEBIAN_TARGETS contains individual debian targets.
DEBIAN_TARGETS: List[Target] = [
Debian("arm64", OONIProbeLinux("arm64")),
Debian("amd64", OONIProbeLinux("amd64")),
]
# DEBIAN is the top-level "debian" target.
DEBIAN = Phony("debian", DEBIAN_TARGETS)
# VISIBLE_TARGETS contains all the visible-from-CLI targets
VISIBLE_TARGETS: List[Target] = (
OONIPROBE_TARGETS
+ OONIPROBE_SIGNED_TARGETS
+ MOBILE_TARGETS
+ EXTRA_TARGETS
+ MINIOONI_TARGETS
+ [OONIPROBE_RELEASE_DARWIN]
+ [OONIPROBE_RELEASE_LINUX]
+ [OONIPROBE_RELEASE_WINDOWS]
+ DEBIAN_TARGETS
+ [DEBIAN]
)
def expand_targets(targets: List[Target]) -> Dict[str, Target]:
"""expand_targets creates a dictionary mapping every existing
target name to its implementation."""
out: Dict[str, Target] = {}
for tgt in targets:
out.update(expand_targets(tgt.deps()))
out[tgt.name()] = tgt
return out
def main() -> None:
"""main function"""
toptargets: Dict[str, Target] = dict((t.name(), t) for t in VISIBLE_TARGETS)
options = ConfigFromCLI.parse(list(toptargets.keys()))
alltargets = expand_targets(TOP_TARGETS)
options = ConfigFromCLI.parse(list(alltargets.keys()), TOP_TARGETS)
engine = new_engine(options)
# note that we check whether the target is known in parse()
selected = toptargets[options.target()]
selected = alltargets[options.target()]
selected.build(engine, options)