./make: script to create android builds (#315)
This PR introduces the `./make` script. For now, this script knows how to make Android releases. We have code in https://github.com/ooni/probe-cli/pull/311 that builds other artifacts in this repository. Because our current concern is https://github.com/ooni/probe/issues/1444, in this PR, I have chosen to keep the Android code necessary to fix https://github.com/ooni/probe/issues/1444. Additionally, this PR removes the script to publish to Bintray. This change part of the https://github.com/ooni/probe/issues/1440 cleanup effort.
This commit is contained in:
		
							parent
							
								
									11087db51a
								
							
						
					
					
						commit
						514ad8d0f5
					
				
							
								
								
									
										6
									
								
								MOBILE/android/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								MOBILE/android/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +1,4 @@ | ||||
| /oonimkall-sources.jar | ||||
| /oonimkall.aar | ||||
| /*.aar | ||||
| /*.asc | ||||
| /*.jar | ||||
| /*.pom | ||||
|  | ||||
							
								
								
									
										16
									
								
								MOBILE/android/go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										16
									
								
								MOBILE/android/go
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,16 @@ | ||||
| #!/bin/sh | ||||
| 
 | ||||
| # print the fullpath of the go we're using so that it's part | ||||
| # of the build logs and we can easily increase our confidence | ||||
| # that we are using the right go binary. | ||||
| 
 | ||||
| echo -n "$0: checking for go... " | ||||
| go=`command -v go` | ||||
| if [ -z $go ]; then | ||||
|     echo "not found" | ||||
|     exit 1 | ||||
| fi | ||||
| echo "$go" | ||||
| 
 | ||||
| set -x | ||||
| $go "$@" | ||||
							
								
								
									
										24
									
								
								MOBILE/android/gomobile
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								MOBILE/android/gomobile
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,24 @@ | ||||
| #!/bin/sh | ||||
| 
 | ||||
| # print the fullpath of the go/gomobile we're using so that it's part | ||||
| # of the build logs and we can easily increase our confidence | ||||
| # that we are using the right go/gomobile binary. | ||||
| 
 | ||||
| echo -n "$0: checking for go... " | ||||
| go=`command -v go` | ||||
| if [ -z $go ]; then | ||||
|     echo "not found" | ||||
|     exit 1 | ||||
| fi | ||||
| echo "$go" | ||||
| 
 | ||||
| echo -n "$0: checking for gomobile... " | ||||
| gomobile=`command -v gomobile` | ||||
| if [ -z $go ]; then | ||||
|     echo "not found" | ||||
|     exit 1 | ||||
| fi | ||||
| echo "$gomobile" | ||||
| 
 | ||||
| set -x | ||||
| $gomobile "$@" | ||||
							
								
								
									
										804
									
								
								make
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										804
									
								
								make
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,804 @@ | ||||
| #!/usr/bin/env python3 | ||||
| 
 | ||||
| """ Build script for ooniprobe. You can get documentation regarding | ||||
| its usage by running `./make --help`. """ | ||||
| 
 | ||||
| from __future__ import annotations | ||||
| import datetime | ||||
| 
 | ||||
| import getopt | ||||
| import json | ||||
| import os | ||||
| import platform | ||||
| import shlex | ||||
| import shutil | ||||
| import subprocess | ||||
| import sys | ||||
| 
 | ||||
| from typing import Dict | ||||
| from typing import List | ||||
| from typing import NoReturn | ||||
| from typing import Optional | ||||
| from typing import Protocol | ||||
| 
 | ||||
| 
 | ||||
| def android_cmdlinetools_os() -> str: | ||||
|     """android_cmdlinetools_os maps the name of the current OS to the | ||||
|     name used by the android command-line tools file.""" | ||||
|     system = platform.system() | ||||
|     if system == "Linux": | ||||
|         return "linux" | ||||
|     if system == "Darwin": | ||||
|         return "mac" | ||||
|     raise RuntimeError(system) | ||||
| 
 | ||||
| 
 | ||||
| def android_cmdlinetools_version() -> str: | ||||
|     """android_cmdlinetools_version returns the version of the Android | ||||
|     command-line tools that we'd like to download.""" | ||||
|     return "6858069" | ||||
| 
 | ||||
| 
 | ||||
| def android_cmdlinetools_sha256sum() -> str: | ||||
|     """android_cmdlinetools_sha256sum returns the SHA256 sum of the | ||||
|     Android command-line tools zip file.""" | ||||
|     return { | ||||
|         "linux": "87f6dcf41d4e642e37ba03cb2e387a542aa0bd73cb689a9e7152aad40a6e7a08", | ||||
|         "mac": "58a55d9c5bcacd7c42170d2cf2c9ae2889c6797a6128307aaf69100636f54a13", | ||||
|     }[android_cmdlinetools_os()] | ||||
| 
 | ||||
| 
 | ||||
| def cachedir() -> str: | ||||
|     """cachedir returns the directory where we cache the SDKs.""" | ||||
|     return os.path.join(os.path.expandvars("${HOME}"), ".ooniprobe-build") | ||||
| 
 | ||||
| 
 | ||||
| def goversion() -> str: | ||||
|     """goversion is the Go version we use.""" | ||||
|     return "1.16.3" | ||||
| 
 | ||||
| 
 | ||||
| def gopath() -> str: | ||||
|     """gopath is the GOPATH we use.""" | ||||
|     return os.path.expandvars("${HOME}/go") | ||||
| 
 | ||||
| 
 | ||||
| def gosha256sum() -> str: | ||||
|     """gosha256sum returns the SHA256 sum of the Go tarball.""" | ||||
|     return { | ||||
|         "linux": { | ||||
|             "amd64": "951a3c7c6ce4e56ad883f97d9db74d3d6d80d5fec77455c6ada6c1f7ac4776d2", | ||||
|             "arm64": "566b1d6f17d2bc4ad5f81486f0df44f3088c3ed47a3bec4099d8ed9939e90d5d", | ||||
|         }, | ||||
|         "darwin": { | ||||
|             "amd64": "6bb1cf421f8abc2a9a4e39140b7397cdae6aca3e8d36dcff39a1a77f4f1170ac", | ||||
|             "arm64": "f4e96bbcd5d2d1942f5b55d9e4ab19564da4fad192012f6d7b0b9b055ba4208f", | ||||
|         }, | ||||
|     }[goos()][goarch()] | ||||
| 
 | ||||
| 
 | ||||
| def goos() -> str: | ||||
|     """goos returns the GOOS value for the current system.""" | ||||
|     system = platform.system() | ||||
|     if system == "Linux": | ||||
|         return "linux" | ||||
|     if system == "Darwin": | ||||
|         return "darwin" | ||||
|     raise RuntimeError(system) | ||||
| 
 | ||||
| 
 | ||||
| def goarch() -> str: | ||||
|     """goarch returns the GOARCH value for the current system.""" | ||||
|     machine = platform.machine() | ||||
|     if machine in ("arm64", "arm", "386", "amd64"): | ||||
|         return machine | ||||
|     if machine in ("x86", "i386"): | ||||
|         return "386" | ||||
|     if machine == "x86_64": | ||||
|         return "amd64" | ||||
|     if machine == "aarch64": | ||||
|         return "arm64" | ||||
|     raise RuntimeError(machine) | ||||
| 
 | ||||
| 
 | ||||
| def android_ndk_version() -> str: | ||||
|     """android_ndk_version returns the Android NDK version.""" | ||||
|     return "22.1.7171670" | ||||
| 
 | ||||
| 
 | ||||
| def sdkmanager_install_cmd(binpath: str) -> List[str]: | ||||
|     """sdkmanager_install_cmd returns the command line for installing | ||||
|     all the required dependencies using the sdkmanager.""" | ||||
|     return [ | ||||
|         os.path.join(binpath, "sdkmanager"), | ||||
|         "--install", | ||||
|         "build-tools;29.0.3", | ||||
|         "platforms;android-30", | ||||
|         "ndk;{}".format(android_ndk_version()), | ||||
|     ] | ||||
| 
 | ||||
| 
 | ||||
| def log(msg: str) -> None: | ||||
|     """log prints a message on the standard error.""" | ||||
|     print(msg, file=sys.stderr) | ||||
| 
 | ||||
| 
 | ||||
| class Options(Protocol): | ||||
|     """Options contains the configured options.""" | ||||
| 
 | ||||
|     def debugging(self) -> bool: | ||||
|         """debugging indicates whether to pass -x to `go build...`.""" | ||||
| 
 | ||||
|     def dry_run(self) -> bool: | ||||
|         """dry_run indicates whether to execute commands.""" | ||||
| 
 | ||||
|     def target(self) -> str: | ||||
|         """target is the target to build.""" | ||||
| 
 | ||||
|     def verbose(self) -> bool: | ||||
|         """verbose indicates whether to pass -v to `go build...`.""" | ||||
| 
 | ||||
| 
 | ||||
| class ConfigFromCLI: | ||||
|     """ConfigFromCLI parses options from CLI flags.""" | ||||
| 
 | ||||
|     @classmethod | ||||
|     def parse(cls, targets: List[str]) -> ConfigFromCLI: | ||||
|         """parse parses command line options and returns a | ||||
|         suitable configuration object.""" | ||||
|         conf = cls() | ||||
|         conf._parse(targets) | ||||
|         return conf | ||||
| 
 | ||||
|     def __init__(self) -> None: | ||||
|         self._debugging = False | ||||
|         self._dry_run = False | ||||
|         self._target = "" | ||||
|         self._verbose = False | ||||
| 
 | ||||
|     def debugging(self) -> bool: | ||||
|         return self._debugging | ||||
| 
 | ||||
|     def dry_run(self) -> bool: | ||||
|         return self._dry_run | ||||
| 
 | ||||
|     def target(self) -> str: | ||||
|         return self._target | ||||
| 
 | ||||
|     def verbose(self) -> bool: | ||||
|         return self._verbose | ||||
| 
 | ||||
|     # The main reason why I am using getopt here is such that I am able | ||||
|     # to print a very clear and detailed usage string. (But the same | ||||
|     # could be obtained quite likely w/ argparse.) | ||||
| 
 | ||||
|     __usage_string = """\ | ||||
| usage: ./make [-nvx] -t target | ||||
|        ./make -l | ||||
|        ./make [--help|-h] | ||||
| 
 | ||||
| The first form of the command builds the `target` specified using the | ||||
| `-t` command line flag. If the target has dependencies, this command will | ||||
| build the dependent targets first. The `-n` flag enables a dry run where | ||||
| the command only prints the commands it would run. The `-v` and `-x` flags | ||||
| are passed directly to `go build ...` and `gomobile bind ...`. | ||||
| 
 | ||||
| The second form of the command lists all the available targets as | ||||
| a pretty-printed JSON list. | ||||
| 
 | ||||
| The third form of the command prints this help screen. | ||||
| """ | ||||
| 
 | ||||
|     @classmethod | ||||
|     def _usage(cls, err: str = "", exitcode: int = 0) -> NoReturn: | ||||
|         if err: | ||||
|             sys.stderr.write("error: {}\n".format(err)) | ||||
|         sys.stderr.write(cls.__usage_string) | ||||
|         sys.exit(exitcode) | ||||
| 
 | ||||
|     def _parse(self, targets: List[str]): | ||||
|         try: | ||||
|             opts, args = getopt.getopt(sys.argv[1:], "hlnt:vx", ["help"]) | ||||
|         except getopt.GetoptError as err: | ||||
|             self._usage(err=err.msg, exitcode=1) | ||||
|         if args: | ||||
|             self._usage(err="unexpected number of positional arguments", exitcode=1) | ||||
|         for key, value in opts: | ||||
|             if key in ("-h", "--help"): | ||||
|                 self._usage() | ||||
|             if key == "-l": | ||||
|                 sys.stdout.write("{}\n".format(json.dumps(targets, indent=4))) | ||||
|                 sys.exit(0) | ||||
|             if key == "-n": | ||||
|                 self._dry_run = True | ||||
|                 continue | ||||
|             if key == "-t": | ||||
|                 self._target = value | ||||
|                 continue | ||||
|             if key == "-v": | ||||
|                 self._verbose = True | ||||
|                 continue | ||||
|             if key == "-x": | ||||
|                 self._debugging = True | ||||
|                 continue | ||||
|             raise RuntimeError(key, value) | ||||
| 
 | ||||
|         if self._target == "":  # no arguments is equivalent to --help | ||||
|             self._usage() | ||||
| 
 | ||||
|         if self._target not in targets: | ||||
|             sys.stderr.write("unknown target: {}\n".format(self._target)) | ||||
|             sys.stderr.write("try `./make -l` to see the available targets.\n") | ||||
|             sys.exit(1) | ||||
| 
 | ||||
| 
 | ||||
| class Engine(Protocol): | ||||
|     """Engine is an engine for building targets.""" | ||||
| 
 | ||||
|     def cat_sed_redirect( | ||||
|         self, pattern: str, value: str, source: str, dest: str | ||||
|     ) -> None: | ||||
|         """cat_sed_redirect does `cat $source|sed "s/$pattern/$value/g" > $dest`.""" | ||||
| 
 | ||||
|     def echo_to_file(self, content: str, filepath: str) -> None: | ||||
|         """echo_to_file writes the content string to the given file.""" | ||||
| 
 | ||||
|     def require(self, *executable: str) -> None: | ||||
|         """require fails if executable is not found in path.""" | ||||
| 
 | ||||
|     def run( | ||||
|         self, | ||||
|         cmdline: List[str], | ||||
|         cwd: Optional[str] = None, | ||||
|         extra_env: Optional[Dict[str, str]] = None, | ||||
|         inputbytes: Optional[bytes] = None, | ||||
|     ) -> None: | ||||
|         """run runs the specified command line.""" | ||||
| 
 | ||||
| 
 | ||||
| class CommandRealExecutor: | ||||
|     """CommandRealExecutor executes commands.""" | ||||
| 
 | ||||
|     def cat_sed_redirect( | ||||
|         self, pattern: str, value: str, source: str, dest: str | ||||
|     ) -> None: | ||||
|         """cat_sed_redirect implements Engine.cat_sed_redirect.""" | ||||
|         with open(source, "r") as sourcefp: | ||||
|             data = sourcefp.read().replace(pattern, value) | ||||
|             with open(dest, "w") as destfp: | ||||
|                 destfp.write(data) | ||||
| 
 | ||||
|     def echo_to_file(self, content: str, filepath: str) -> None: | ||||
|         """echo_to_file implements Engine.echo_to_file""" | ||||
|         with open(filepath, "w") as filep: | ||||
|             filep.write(content) | ||||
|             filep.write("\n") | ||||
| 
 | ||||
|     def require(self, *executable: str) -> None: | ||||
|         """require implements Engine.require.""" | ||||
|         # Implemented in CommandDryRunner | ||||
| 
 | ||||
|     def run( | ||||
|         self, | ||||
|         cmdline: List[str], | ||||
|         cwd: Optional[str] = None, | ||||
|         extra_env: Optional[Dict[str, str]] = None, | ||||
|         inputbytes: Optional[bytes] = None, | ||||
|     ) -> None: | ||||
|         """run implements Engine.run.""" | ||||
|         env = os.environ.copy() | ||||
|         if extra_env: | ||||
|             for key, value in extra_env.items(): | ||||
|                 env[key] = value | ||||
|         subprocess.run(cmdline, check=True, cwd=cwd, env=env, input=inputbytes) | ||||
| 
 | ||||
| 
 | ||||
| class CommandDryRunner: | ||||
|     """CommandDryRunner is the dry runner.""" | ||||
| 
 | ||||
|     # Implementation note: here we try to log valid bash snippets | ||||
|     # such that is really obvious what we are doing. | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.__cmdcache: Dict[str, str] = {} | ||||
| 
 | ||||
|     def cat_sed_redirect( | ||||
|         self, pattern: str, value: str, source: str, dest: str | ||||
|     ) -> None: | ||||
|         """cat_sed_redirect implements Engine.cat_sed_redirect.""" | ||||
|         log('./make: cat {}|sed "s/{}/{}/g" > {}'.format(source, pattern, value, dest)) | ||||
| 
 | ||||
|     def echo_to_file(self, content: str, filepath: str) -> None: | ||||
|         """echo_to_file implements Engine.echo_to_file""" | ||||
|         log("./make: echo '{}' > {}".format(content, filepath)) | ||||
| 
 | ||||
|     def require(self, *executable: str) -> None: | ||||
|         """require implements Engine.require.""" | ||||
|         for exc in executable: | ||||
|             if exc in self.__cmdcache: | ||||
|                 continue  # do not print checks more than once | ||||
|             fullpath = shutil.which(exc) | ||||
|             if not fullpath: | ||||
|                 log("./make: checking for {}... not found".format(exc)) | ||||
|                 sys.exit(1) | ||||
|             log("./make: checking for {}... {}".format(exc, fullpath)) | ||||
|             self.__cmdcache[exc] = fullpath | ||||
| 
 | ||||
|     def run( | ||||
|         self, | ||||
|         cmdline: List[str], | ||||
|         cwd: Optional[str] = None, | ||||
|         extra_env: Optional[Dict[str, str]] = None, | ||||
|         inputbytes: Optional[bytes] = None, | ||||
|     ) -> None: | ||||
|         """run implements Engine.run.""" | ||||
|         cdpart = "" | ||||
|         if cwd: | ||||
|             cdpart = "cd {} && ".format(cwd) | ||||
|         envpart = "" | ||||
|         if extra_env: | ||||
|             for key, value in extra_env.items(): | ||||
|                 envpart += shlex.join(["{}={}".format(key, value)]) + " " | ||||
|         log("./make: {}{}{}".format(cdpart, envpart, shlex.join(cmdline))) | ||||
| 
 | ||||
| 
 | ||||
| class EngineComposer: | ||||
|     """EngineComposer composes two engines.""" | ||||
| 
 | ||||
|     def __init__(self, first: Engine, second: Engine): | ||||
|         self._first = first | ||||
|         self._second = second | ||||
| 
 | ||||
|     def cat_sed_redirect( | ||||
|         self, pattern: str, value: str, source: str, dest: str | ||||
|     ) -> None: | ||||
|         """cat_sed_redirect implements Engine.cat_sed_redirect.""" | ||||
|         self._first.cat_sed_redirect(pattern, value, source, dest) | ||||
|         self._second.cat_sed_redirect(pattern, value, source, dest) | ||||
| 
 | ||||
|     def echo_to_file(self, content: str, filepath: str) -> None: | ||||
|         """echo_to_file implements Engine.echo_to_file""" | ||||
|         self._first.echo_to_file(content, filepath) | ||||
|         self._second.echo_to_file(content, filepath) | ||||
| 
 | ||||
|     def require(self, *executable: str) -> None: | ||||
|         """require implements Engine.require.""" | ||||
|         self._first.require(*executable) | ||||
|         self._second.require(*executable) | ||||
| 
 | ||||
|     def run( | ||||
|         self, | ||||
|         cmdline: List[str], | ||||
|         cwd: Optional[str] = None, | ||||
|         extra_env: Optional[Dict[str, str]] = None, | ||||
|         inputbytes: Optional[bytes] = None, | ||||
|     ) -> None: | ||||
|         """run implements Engine.run.""" | ||||
|         self._first.run(cmdline, cwd=cwd, extra_env=extra_env, inputbytes=inputbytes) | ||||
|         self._second.run(cmdline, cwd=cwd, extra_env=extra_env, inputbytes=inputbytes) | ||||
| 
 | ||||
| 
 | ||||
| def new_engine(options: Options) -> Engine: | ||||
|     """new_engine creates a new engine instance""" | ||||
|     if options.dry_run(): | ||||
|         return CommandDryRunner() | ||||
|     return EngineComposer(CommandDryRunner(), CommandRealExecutor()) | ||||
| 
 | ||||
| 
 | ||||
| class Target(Protocol): | ||||
|     """Target is a target to build.""" | ||||
| 
 | ||||
|     def name(self) -> str: | ||||
|         """name returns the target name.""" | ||||
| 
 | ||||
|     def build(self, engine: Engine, options: Options) -> None: | ||||
|         """build builds the specified target.""" | ||||
| 
 | ||||
| 
 | ||||
| class SDKGolangGo: | ||||
|     """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 build(self, engine: Engine, options: Options) -> None: | ||||
|         if os.path.isdir(self.__name) and not options.dry_run(): | ||||
|             log("./make: {}: already built".format(self.__name)) | ||||
|             return | ||||
|         log("./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(["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(["rm", filepath]) | ||||
| 
 | ||||
|     def goroot(self): | ||||
|         """goroot returns the goroot.""" | ||||
|         return os.path.join(self.__name, "go") | ||||
| 
 | ||||
| 
 | ||||
| class SDKOONIGo: | ||||
|     """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 binpath(self) -> str: | ||||
|         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("./make: {}: already built".format(self.__name)) | ||||
|             return | ||||
|         golang_go = SDKGolangGo() | ||||
|         golang_go.build(engine, options) | ||||
|         log("./make: building {}...".format(self.__name)) | ||||
|         engine.require("git", "bash") | ||||
|         engine.run( | ||||
|             [ | ||||
|                 "git", | ||||
|                 "clone", | ||||
|                 "-b", | ||||
|                 "ooni", | ||||
|                 "--single-branch", | ||||
|                 "--depth", | ||||
|                 "8", | ||||
|                 "https://github.com/ooni/go", | ||||
|                 self.__name, | ||||
|             ] | ||||
|         ) | ||||
|         engine.run( | ||||
|             ["./make.bash"], | ||||
|             cwd=os.path.join(self.__name, "src"), | ||||
|             extra_env={"GOROOT_BOOTSTRAP": golang_go.goroot()}, | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| class SDKAndroid: | ||||
|     """SDKAndroid creates ${cachedir}/SDK/android.""" | ||||
| 
 | ||||
|     __name = os.path.join(cachedir(), "SDK", "android") | ||||
| 
 | ||||
|     def name(self) -> str: | ||||
|         return self.__name | ||||
| 
 | ||||
|     def home(self) -> str: | ||||
|         return self.__name | ||||
| 
 | ||||
|     def ndk_home(self) -> str: | ||||
|         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("./make: {}: already built".format(self.__name)) | ||||
|             return | ||||
|         log("./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(["curl", "-fsSLo", filepath, url]) | ||||
|         sha256file = os.path.join(cachedir(), "SDK", "SHA256") | ||||
|         engine.echo_to_file( | ||||
|             "{}  {}".format(android_cmdlinetools_sha256sum(), filepath), sha256file | ||||
|         ) | ||||
|         engine.run(["shasum", "--check", sha256file]) | ||||
|         engine.run(["rm", sha256file]) | ||||
|         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 | ||||
|         ) | ||||
|         engine.run(["mkdir", "cmdline-tools"], cwd=self.__name) | ||||
|         engine.run( | ||||
|             ["mv", android_cmdlinetools_version(), "cmdline-tools"], cwd=self.__name | ||||
|         ) | ||||
|         engine.run( | ||||
|             sdkmanager_install_cmd( | ||||
|                 os.path.join( | ||||
|                     self.__name, | ||||
|                     "cmdline-tools", | ||||
|                     android_cmdlinetools_version(), | ||||
|                     "bin", | ||||
|                 ), | ||||
|             ), | ||||
|             inputbytes=b"Y\n",  # automatically accept license | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| class OONIProbePrivate: | ||||
|     """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 copyfiles(self, engine: Engine) -> None: | ||||
|         """copyfiles copies psiphon config to the repository.""" | ||||
|         engine.run( | ||||
|             [ | ||||
|                 "cp", | ||||
|                 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("internal", "engine"), | ||||
|             ] | ||||
|         ) | ||||
| 
 | ||||
|     def build(self, engine: Engine, options: Options) -> None: | ||||
|         if os.path.isdir(self.__name) and not options.dry_run(): | ||||
|             log("./make: {}: already built".format(self.__name)) | ||||
|             return | ||||
|         log("./make: building {}...".format(self.__name)) | ||||
|         engine.require("git", "cp") | ||||
|         engine.run( | ||||
|             [ | ||||
|                 "git", | ||||
|                 "clone", | ||||
|                 "git@github.com:ooni/probe-private", | ||||
|                 self.__name, | ||||
|             ] | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| class OONIMKAllAAR: | ||||
|     """OONIMKAllAAR creates ./MOBILE/android/oonimkall.aar.""" | ||||
| 
 | ||||
|     __name = os.path.join(".", "MOBILE", "android", "oonimkall.aar") | ||||
| 
 | ||||
|     def name(self) -> str: | ||||
|         return self.__name | ||||
| 
 | ||||
|     def aarfile(self) -> str: | ||||
|         return self.__name | ||||
| 
 | ||||
|     def srcfile(self) -> str: | ||||
|         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("./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("./make: building {}...".format(self.__name)) | ||||
|         ooprivate.copyfiles(engine) | ||||
|         engine.require("sh", "javac") | ||||
|         self._go_get_gomobile(engine, options, oonigo) | ||||
|         self._gomobile_init(engine, oonigo, android) | ||||
|         self._gomobile_bind(engine, options, oonigo, android) | ||||
| 
 | ||||
|     # Implementation note: we use proxy scripts for go and gomobile | ||||
|     # that explicitly print what they resolve go and gomobile to using | ||||
|     # `command -v`. This gives us extra confidence that we are really | ||||
|     # using the oonigo fork of golang/go. | ||||
| 
 | ||||
|     def _go_get_gomobile( | ||||
|         self, engine: Engine, options: Options, oonigo: SDKOONIGo | ||||
|     ) -> None: | ||||
|         # TODO(bassosimone): find a way to run this command without | ||||
|         # adding extra dependencies to go.mod and go.sum. | ||||
|         cmdline: List[str] = [] | ||||
|         cmdline.append(os.path.join(".", "MOBILE", "android", "go")) | ||||
|         cmdline.append("get") | ||||
|         cmdline.append("-u") | ||||
|         if options.verbose(): | ||||
|             cmdline.append("-v") | ||||
|         if options.debugging(): | ||||
|             cmdline.append("-x") | ||||
|         cmdline.append("golang.org/x/mobile/cmd/gomobile@latest") | ||||
|         engine.run( | ||||
|             cmdline, | ||||
|             extra_env={ | ||||
|                 "PATH": os.pathsep.join( | ||||
|                     [ | ||||
|                         oonigo.binpath(),  # so we use our go fork | ||||
|                         os.environ["PATH"],  # original path | ||||
|                     ] | ||||
|                 ), | ||||
|                 "GOPATH": gopath(),  # where to install gomobile | ||||
|             }, | ||||
|         ) | ||||
| 
 | ||||
|     def _gomobile_init( | ||||
|         self, | ||||
|         engine: Engine, | ||||
|         oonigo: SDKOONIGo, | ||||
|         android: SDKAndroid, | ||||
|     ) -> None: | ||||
|         cmdline: List[str] = [] | ||||
|         cmdline.append(os.path.join(".", "MOBILE", "android", "gomobile")) | ||||
|         cmdline.append("init") | ||||
|         engine.run( | ||||
|             cmdline, | ||||
|             extra_env={ | ||||
|                 "PATH": os.pathsep.join( | ||||
|                     [ | ||||
|                         os.path.join(gopath(), "bin"),  # for gomobile | ||||
|                         oonigo.binpath(),  # for our go fork | ||||
|                         os.environ["PATH"],  # original environment | ||||
|                     ] | ||||
|                 ), | ||||
|                 "ANDROID_HOME": android.home(), | ||||
|                 "ANDROID_NDK_HOME": android.ndk_home(), | ||||
|             }, | ||||
|         ) | ||||
| 
 | ||||
|     def _gomobile_bind( | ||||
|         self, | ||||
|         engine: Engine, | ||||
|         options: Options, | ||||
|         oonigo: SDKOONIGo, | ||||
|         android: SDKAndroid, | ||||
|     ) -> None: | ||||
|         cmdline: List[str] = [] | ||||
|         cmdline.append(os.path.join(".", "MOBILE", "android", "gomobile")) | ||||
|         cmdline.append("bind") | ||||
|         if options.verbose(): | ||||
|             cmdline.append("-v") | ||||
|         if options.debugging(): | ||||
|             cmdline.append("-x") | ||||
|         cmdline.append("-target") | ||||
|         cmdline.append("android") | ||||
|         cmdline.append("-o") | ||||
|         cmdline.append(self.__name) | ||||
|         cmdline.append("-tags") | ||||
|         cmdline.append("ooni_psiphon_config") | ||||
|         cmdline.append("-ldflags") | ||||
|         cmdline.append("-s -w") | ||||
|         cmdline.append("./pkg/oonimkall") | ||||
|         engine.run( | ||||
|             cmdline, | ||||
|             extra_env={ | ||||
|                 "PATH": os.pathsep.join( | ||||
|                     [ | ||||
|                         os.path.join(gopath(), "bin"),  # for gomobile | ||||
|                         oonigo.binpath(),  # for our go fork | ||||
|                         os.environ["PATH"],  # original environment | ||||
|                     ] | ||||
|                 ), | ||||
|                 "ANDROID_HOME": android.home(), | ||||
|                 "ANDROID_NDK_HOME": android.ndk_home(), | ||||
|             }, | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| class BundleJAR: | ||||
|     """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 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)) | ||||
|             return | ||||
|         oonimkall = OONIMKAllAAR() | ||||
|         oonimkall.build(engine, options) | ||||
|         log("./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(), | ||||
|                 os.path.join("MOBILE", "android", "oonimkall-{}.aar".format(version)), | ||||
|             ] | ||||
|         ) | ||||
|         engine.run( | ||||
|             [ | ||||
|                 "cp", | ||||
|                 oonimkall.srcfile(), | ||||
|                 os.path.join( | ||||
|                     "MOBILE", "android", "oonimkall-{}-sources.jar".format(version) | ||||
|                 ), | ||||
|             ] | ||||
|         ) | ||||
|         engine.cat_sed_redirect( | ||||
|             "@VERSION@", | ||||
|             version, | ||||
|             os.path.join("MOBILE", "template.pom"), | ||||
|             os.path.join("MOBILE", "android", "oonimkall-{}.pom".format(version)), | ||||
|         ) | ||||
|         names = ( | ||||
|             "oonimkall-{}.aar".format(version), | ||||
|             "oonimkall-{}-sources.jar".format(version), | ||||
|             "oonimkall-{}.pom".format(version), | ||||
|         ) | ||||
|         for name in names: | ||||
|             engine.run( | ||||
|                 [ | ||||
|                     "gpg", | ||||
|                     "-abu", | ||||
|                     "simone@openobservatory.org", | ||||
|                     name, | ||||
|                 ], | ||||
|                 cwd=os.path.join(".", "MOBILE", "android"), | ||||
|             ) | ||||
|         allnames = [name + ".asc" for name in names] | ||||
|         allnames.extend(names) | ||||
|         engine.run( | ||||
|             [ | ||||
|                 "jar", | ||||
|                 "-cf", | ||||
|                 "bundle.jar", | ||||
|                 *allnames, | ||||
|             ], | ||||
|             cwd=os.path.join(".", "MOBILE", "android"), | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| class Android: | ||||
|     """Android is the toplevel android target.""" | ||||
| 
 | ||||
|     def name(self) -> str: | ||||
|         return "android" | ||||
| 
 | ||||
|     def build(self, engine: Engine, options: Options) -> None: | ||||
|         bundlejar = BundleJAR() | ||||
|         bundlejar.build(engine, options) | ||||
| 
 | ||||
| 
 | ||||
| TARGETS: List[Target] = [ | ||||
|     Android(), | ||||
|     BundleJAR(), | ||||
|     OONIMKAllAAR(), | ||||
|     OONIProbePrivate(), | ||||
|     SDKAndroid(), | ||||
|     SDKGolangGo(), | ||||
|     SDKOONIGo(), | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| def main() -> None: | ||||
|     """main function""" | ||||
|     alltargets: Dict[str, Target] = dict((t.name(), t) for t in TARGETS) | ||||
|     options = ConfigFromCLI.parse(list(alltargets.keys())) | ||||
|     engine = new_engine(options) | ||||
|     # note that we check whether the target is known in parse() | ||||
|     selected = alltargets[options.target()] | ||||
|     selected.build(engine, options) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
| @ -1,28 +0,0 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
| pkgname=oonimkall | ||||
| version=$(date -u +%Y.%m.%d-%H%M%S) | ||||
| baseurl=https://api.bintray.com/content/ooni/android/$pkgname/$version/org/ooni/$pkgname/$version | ||||
| aarfile=./MOBILE/android/$pkgname.aar | ||||
| aarfile_version=./MOBILE/android/$pkgname-$version.aar | ||||
| ln $aarfile $aarfile_version | ||||
| sourcesfile=./MOBILE/android/$pkgname-sources.jar | ||||
| sourcesfile_version=./MOBILE/android/$pkgname-$version-sources.jar | ||||
| ln $sourcesfile $sourcesfile_version | ||||
| pomfile=./MOBILE/android/$pkgname-$version.pom | ||||
| pomtemplate=./MOBILE/template.pom | ||||
| user=bassosimone | ||||
| cat $pomtemplate|sed "s/@VERSION@/$version/g" > $pomfile | ||||
| if [ -z $MOBILE_BINTRAY_API_KEY ]; then | ||||
|     echo "FATAL: missing MOBILE_BINTRAY_API_KEY variable" 1>&2 | ||||
|     exit 1 | ||||
| fi | ||||
| # We currently publish the mobile-staging branch. To cleanup we can fetch all the versions using | ||||
| # the <curl -s $user:$MOBILE_BINTRAY_API_KEY https://api.bintray.com/packages/ooni/android/oonimkall> | ||||
| # query, which returns a list of versions. From such list, we can delete the versions we | ||||
| # don't need using <DELETE /packages/:subject/:repo/:package/versions/:version>. | ||||
| for filename in $aarfile_version $sourcesfile_version $pomfile; do | ||||
|   basefilename=$(basename $filename) | ||||
|   curl -sT $filename -u $user:$MOBILE_BINTRAY_API_KEY $baseurl/$basefilename?publish=1 >/dev/null | ||||
| done | ||||
| echo "implementation 'org.ooni:oonimkall:$version'" | ||||
| @ -1,23 +0,0 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
| pkgname=oonimkall | ||||
| version=$(date -u +%Y.%m.%d-%H%M%S) | ||||
| baseurl=https://api.bintray.com/content/ooni/ios/$pkgname/$version | ||||
| framework=./MOBILE/ios/$pkgname.framework | ||||
| frameworkzip=./MOBILE/ios/$pkgname.framework.zip | ||||
| podspecfile=./MOBILE/ios/$pkgname.podspec | ||||
| podspectemplate=./MOBILE/template.podspec | ||||
| user=bassosimone | ||||
| (cd ./MOBILE/ios && rm -f $pkgname.framework.zip && zip -yr $pkgname.framework.zip $pkgname.framework) | ||||
| cat $podspectemplate|sed "s/@VERSION@/$version/g" > $podspecfile | ||||
| if [ -z $MOBILE_BINTRAY_API_KEY ]; then | ||||
|     echo "FATAL: missing MOBILE_BINTRAY_API_KEY variable" 1>&2 | ||||
|     exit 1 | ||||
| fi | ||||
| # We currently publish the mobile-staging branch. To cleanup we can fetch all the versions using | ||||
| # the <curl -s $user:$MOBILE_BINTRAY_API_KEY https://api.bintray.com/packages/ooni/android/oonimkall> | ||||
| # query, which returns a list of versions. From such list, we can delete the versions we | ||||
| # don't need using <DELETE /packages/:subject/:repo/:package/versions/:version>. | ||||
| curl -sT $frameworkzip -u $user:$MOBILE_BINTRAY_API_KEY $baseurl/$pkgname-$version.framework.zip?publish=1 >/dev/null | ||||
| curl -sT $podspecfile -u $user:$MOBILE_BINTRAY_API_KEY $baseurl/$pkgname-$version.podspec?publish=1 >/dev/null | ||||
| echo "pod 'oonimkall', :podspec => 'https://dl.bintray.com/ooni/ios/$pkgname-$version.podspec'" | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user