Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/bc/scripts/release.pkg.yao
39492 views
/**
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2018-2025 Gavin D. Howard and contributors.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

PROG: str = path.realpath(sys.program);
REAL: str = path.realpath(PROG +~ "..");

SCRIPTDIR: str = path.dirname(PROG);

WINDOWS: bool = (host.os == "Windows");

DEFOPT: str = if WINDOWS { "/D"; } else { "-D"; };
C11OPT: str = if WINDOWS { "/std:c11"; } else { "-std=c11"; };
C99OPT: str = if WINDOWS { "/std:c99"; } else { "-std=c99"; };

/**
 * Turns a string array into a string by joining all strings together with
 * spaces.
 * @param arr  The array to join.
 * @return     The joined array.
 */
fn strv2str(arr: []str) -> str
{
	ret: !str = "";

	for item: arr
	{
		if ret == ""
		{
			ret! = item;
		}
		else
		{
			ret! = ret +~ " " +~ item;
		}
	}

	return ret;
}

opts: Gaml = @(gaml){
	help: [
		"Runs the testing part of the release process.\n"
		"\n"
		"Usage: ", $PROG, " [-h|--help] [<options> \n"
	]
	options: {
		no64: {
			help: "Turn off building and testing 64-bit builds."
			long: @no-64
			type: @NONE
		}
		no128: {
			help: "Turn off building and testing builds with 128-bit integers."
			long: @no-128
			type: @NONE
		}
		C: {
			help: "Turn off building under Clang."
			short: @C
			long: @no-clang
			type: @NONE
		}
		C11: {
			help: "Turn off building under C11."
			long: @no-c11
			type: @NONE
		}
		G: {
			help: "Turn off building under GCC."
			short: @G
			long: @no-gcc
			type: @NONE
		}
		GEN: {
			help: "Turn off running the gen script."
			short: @G
			long: @no-gen-script
			type: @NONE
		}
		MAKE: {
			help: "Turn off running the build with `configure.sh` and `make`."
			long: @no-make
			type: @NONE
		}
		RIG: {
			help: "Turn off running the build with Rig."
			long: @no-rig
			type: @NONE
		}
		T: {
			help: "Turn off running tests."
			short: @T
			long: @no-tests
			type: @NONE
		}
		computed-goto: {
			help: "Whether to test with computed goto or not."
			long: @computed-goto
			type: @NONE
		}
		e: {
			help: "Whether to test with editline or not."
			short: @e
			long: @editline
			type: @NONE
		}
		g: {
			help: "Whether generate tests that are generated or not."
			short: @g
			long: @generate
			type: @NONE
		}
		history: {
			help: "Whether to test with history or not."
			long: @history
			type: @NONE
		}
		k: {
			help: "Whether to run the Karatsuba tests or not."
			short: @k
			long: @karatsuba
			type: @NONE
		}
		p: {
			help: "Whether to run problematic tests or not."
			short: @p
			long: @problematic-tests
			type: @NONE
		}
		r: {
			help: "Whether to test with readline or not."
			short: @r
			long: @readline
			type: @NONE
		}
		s: {
			help: "Whether to run tests under sanitizers or not."
			short: @s
			long: @sanitizers
			type: @NONE
		}
		settings: {
			help: "Whether to test settings or not."
			long: @settings
			type: @NONE
		}
		v: {
			help: "Whether to run under Valgrind or not."
			short: @v
			long: @valgrind
			type: @NONE
		}
	}
};

// These are the booleans for the command-line flags.
no64: !bool = false;
no128: !bool = false;
clang: !bool = true;
c11: !bool = true;
gcc: !bool = true;
gen_host: !bool = true;
run_make: !bool = !WINDOWS;
run_rig: !bool = true;
tests: !bool = true;
computed_goto: !bool = false;
editline: !bool = false;
generated: !bool = false;
history: !bool = false;
karatsuba: !bool = false;
problematic: !bool = false;
readline: !bool = false;
sanitizers: !bool = false;
settings: !bool = false;
valgrind: !bool = false;

defcc: str = if clang { "clang"; } else if gcc { "gcc"; } else { "c99"; };
defbits: usize = if no64 { usize(32); } else { usize(64); };

cgoto_flags: []str = if !computed_goto { @[ "-DBC_NO_COMPUTED_GOTO" ]; };

// Set some strict warning flags. Clang's -Weverything can be way too strict, so
// we actually have to turn off some things.
CLANG_FLAGS: []str = @[ "-Weverything", "-Wno-padded",
                        "-Wno-unsafe-buffer-usage",
                        "-Wno-poison-system-directories",
                        "-Wno-switch-default", "-Wno-unknown-warning-option",
                        "-Wno-pre-c11-compat" ] +~ cgoto_flags;
GCC_FLAGS: []str = @[ "-Wno-clobbered" ] +~ cgoto_flags;

CLANG_FLAGS_STR: str = strv2str(CLANG_FLAGS);

GCC_FLAGS_STR: str = strv2str(GCC_FLAGS);

// Common CFLAGS.
CFLAGS: []str = @[ "-Wall", "-Wextra", "-Werror", "-pedantic" ];

CFLAGS_STR: str = strv2str(CFLAGS);

// Common debug and release flags.
DEBUG_CFLAGS: []str = CFLAGS +~ @[ "-fno-omit-frame-pointer" ];
DEBUG_CFLAGS_STR: str = CFLAGS_STR +~ " -fno-omit-frame-pointer";
RELEASE_CFLAGS: []str = CFLAGS +~ @[ "-DNDEBUG" ];
RELEASE_CFLAGS_STR: str = CFLAGS_STR +~ " -DNDEBUG";

/**
 * Print a header with a message. This is just to make it easy to track
 * progress.
 * @param msg  The message to print in the header.
 */
fn header(msg: str) -> void
{
	io.eprint("\n*******************\n");
	io.eprint(msg);
	io.eprint("\n*******************\n\n");
}

/**
 * An easy way to call `make`.
 * @param args  The arguments to pass to `make`, if any.
 */
fn do_make(args: []str) -> void
{
	$ make -j64 %(args);
}

/**
 * An easy way to call Rig.
 * @param args  The arguments to pass to Rig, if any.
 */
fn do_rig(args: []str) -> void
{
	$ rig -j64 %(args);
}

/**
 * Runs `configure.sh`.
 * @param cflags    The C compiler flags.
 * @param cc        The C compiler.
 * @param flags     The flags to pass to `configure.sh` itself.
 * @param gen       The setting for `GEN_HOST`.
 * @param long_bit  The number of bits in longs.
 */
fn configure(
	cflags: str,
	cc: str,
	flags: str,
	gen: bool,
	long_bit: usize
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	extra_flags: str =
	if !generated
	{
		if !problematic { "-GP"; } else { "-G"; }
	}
	else if !problematic
	{
		"-P";
	};

	extra_cflags: str =
	if cc == "clang"
	{
		// We need to quiet this warning from Clang because the configure.sh
		// docs have this warning, so people should know. Also, I want this
		// script to work.
		CLANG_FLAGS_STR +~ (if !gen_host { " -Wno-overlength-strings"; });
	}
	else if cc == "gcc"
	{
		// We need to quiet this warning from GCC because the configure.sh docs
		// have this warning, so people should know. Also, I want this script to
		// work.
		GCC_FLAGS_STR +~ (if !gen_host { "-Wno-overlength-strings"; });
	};

	all_flags: str = flags +~ " " +~ extra_flags;
	all_cflags: str = cflags +~ " " +~ extra_cflags;

	hdr := "Running configure.sh " +~ all_flags +~
	       "..." +~
	       "\n    CC=\"" +~ cc +~ "\"\n" +~
	       "\n    CFLAGS=\"" +~ all_cflags +~ "\"\n" +~
	       "\n    LONG_BIT=" +~ str(long_bit) +~
	       "\n    GEN_HOST=" +~ str(gen);

	// Print the header and do the job.
	header(hdr);

	env.set env.str("CFLAGS", str(cflags +~ extra_cflags)),
	        env.str("CC", cc), env.str("GEN_HOST", str(gen)),
	        env.str("LONG_BIT", str(long_bit))
	{
		$ @(path.join(REAL, "configure.sh")) $flags $extra_flags > /dev/null
		  !> /dev/null;
	}
}

/**
 * Build with `make`. This function also captures and outputs any warnings if
 * they exist because as far as I am concerned, warnings are not acceptable for
 * release.
 * @param cflags    The C compiler flags.
 * @param cc        The C compiler.
 * @param flags     The flags to pass to `configure.sh` itself.
 * @param gen       The setting for `GEN_HOST`.
 * @param long_bit  The number of bits in longs.
 */
fn build_make(
	cflags: str,
	cc: str,
	flags: str,
	gen: bool,
	long_bit: usize
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	configure(cflags, cc, flags, gen, long_bit);

	hdr := "Building with `make`...\n    CC=" +~ cc +~
	       "\n    CFLAGS=\"" +~ cflags +~
	       "\n    LONG_BIT=" +~ str(long_bit) +~
	       "\n    GEN_HOST=" +~ str(gen);

	header(hdr);

	res := $ make;

	if res.stderr.len != 0
	{
		io.eprint(cc +~ "generated warning(s):\n\n");
		io.eprint(str(res.stderr));
		sys.exit(1);
	}

	if res.exitcode != 0
	{
		sys.exit(res.exitcode);
	}
}

/**
 * Build with Rig. This function also captures and outputs any warnings if they
 * exist because as far as I am concerned, warnings are not acceptable for
 * release.
 * @param cflags    The C compiler flags.
 * @param cc        The C compiler.
 * @param flags     The flags to pass to `configure.sh` itself.
 * @param long_bit  The number of bits in longs.
 */
fn build_rig(
	cflags: []str,
	cc: str,
	flags: []str,
	long_bit: usize
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	cflags_str: str = strv2str(cflags);

	hdr := "Building with Rig...\n    CC=" +~ cc +~
	       "\n    CFLAGS=\"" +~ cflags_str +~
	       "\n    LONG_BIT=" +~ str(long_bit);

	header(hdr);

	res := $ rig;

	if res.stderr.len != 0
	{
		io.eprint(cc +~ "generated warning(s):\n\n");
		io.eprint(str(res.stderr));
		sys.exit(1);
	}

	if res.exitcode != 0
	{
		sys.exit(res.exitcode);
	}
}

/**
 * Run tests with `make`.
 * @param args  Any arguments to pass to `make`, if any.
 */
fn run_test_make(args: []str) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	header("Running `make` tests");

	if args.len > 0
	{
		do_make(args);
	}
	else
	{
		do_make(@[ "test" ]);

		if history
		{
			do_make(@[ "test_history" ]);
		}
	}
}

/**
 * Run tests with Rig.
 * @param args  Any arguments to pass to Rig, if any.
 */
fn run_test_rig(args: []str) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	header("Running Rig tests");

	if args.len > 0
	{
		do_rig(args);
	}
	else
	{
		do_rig(@[ "test" ]);

		if history
		{
			do_rig(@[ "test_history" ]);
		}
	}
}

/**
 * Builds and runs tests using `make` with both calculators, then bc only, then
 * dc only. If `tests` is false, then it just does the builds.
 * @param cflags    The C compiler flags.
 * @param cc        The C compiler.
 * @param flags     The flags to pass to `configure.sh` itself.
 * @param gen       The setting for `GEN_HOST`.
 * @param long_bit  The number of bits in longs.
 */
fn run_config_tests_make(
	cflags: str,
	cc: str,
	flags: str,
	gen: bool,
	long_bit: usize,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	header_start: str =
	if tests
	{
		"Running tests with configure flags and `make`";
	}
	else
	{
		"Building with configure flags and `make`";
	};

	header("\"" +~ header_start +~ "\" ...\n" +~
	       "    CC=" +~ cc +~ "\n" +~
	       "    CFLAGS=\"" +~ cflags +~ "\"\n" +~
	       "    LONG_BIT=" +~ str(long_bit) +~ "\n" +~
	       "    GEN_HOST=" +~ str(gen));

	build_make(cflags, cc, flags, gen, long_bit);

	if tests
	{
		run_test_make(@[]);
	}

	do_make(@[ "clean" ]);

	build_make(cflags, cc, flags +~ " -b", gen, long_bit);

	if tests
	{
		run_test_make(@[]);
	}

	do_make(@[ "clean" ]);

	build_make(cflags, cc, flags +~ " -d", gen, long_bit);

	if tests
	{
		run_test_make(@[]);
	}

	do_make(@[ "clean" ]);
}

/**
 * Builds and runs tests using Rig with both calculators, then bc only, then dc
 * only. If `tests` is false, then it just does the builds.
 * @param cflags    The C compiler flags.
 * @param cc        The C compiler.
 * @param flags     The flags to pass to Rig itself.
 * @param long_bit  The number of bits in longs.
 */
fn run_config_tests_rig(
	cflags: []str,
	cc: str,
	flags: []str,
	long_bit: usize,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	header_start: str =
	if tests
	{
		"Running tests with Rig";
	}
	else
	{
		"Building with Rig";
	};

	header("\"" +~ header_start +~ "\" ...\n" +~
	       "    CC=" +~ cc +~ "\n" +~
	       "    CFLAGS=\"" +~ strv2str(cflags) +~ "\"\n" +~
	       "    flags=\"" +~ strv2str(flags) +~ "\"\n" +~
	       "    LONG_BIT=" +~ str(long_bit));

	build_rig(cflags, cc, flags, long_bit);

	if tests
	{
		run_test_rig(@[]);
	}

	do_rig(@[ "clean" ]);

	build_rig(cflags, cc, flags +~ @[ "-Dbuild_mode=bc" ], long_bit);

	if tests
	{
		run_test_rig(@[]);
	}

	do_rig(@[ "clean" ]);

	build_rig(cflags, cc, flags +~ @[ "-Dbuild_mode=dc" ], long_bit);

	if tests
	{
		run_test_rig(@[]);
	}

	do_rig(@[ "clean" ]);
}

/**
 * Builds and runs tests using `run_config_tests_make()` with both calculators,
 * then bc only, then dc only. If `tests` is false, then it just does the
 * builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to `configure.sh` itself.
 */
fn run_config_series_make(
	cflags: str,
	cc: str,
	flags: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	if !no64
	{
		if !no128
		{
			run_config_tests_make(cflags, cc, flags, true, usize(64));
		}

		if gen_host
		{
			run_config_tests_make(cflags, cc, flags, false, usize(64));
		}

		run_config_tests_make(cflags +~ " " +~ DEFOPT +~ "BC_RAND_BUILTIN=0",
		                      cc, flags, true, usize(64));

		// Test Editline if history is not turned off.
		if editline && !(flags contains "H")
		{
			run_config_tests_make(cflags +~ " " +~ DEFOPT +~
			                          "BC_RAND_BUILTIN=0",
			                      cc, flags +~ " -e", true, usize(64));
		}

		// Test Readline if history is not turned off.
		if readline && !(flags contains "H")
		{
			run_config_tests_make(cflags +~ " " +~ DEFOPT +~
			                          "BC_RAND_BUILTIN=0",
			                      cc, flags +~ " -r", true, usize(64));
		}
	}

	run_config_tests_make(cflags, cc, flags, true, usize(32));

	if gen_host
	{
		run_config_tests_make(cflags, cc, flags, false, usize(32));
	}
}

/**
 * Builds and runs tests using `run_config_tests_rig()` with both calculators,
 * then bc only, then dc only. If `tests` is false, then it just does the
 * builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to Rig itself.
 */
fn run_config_series_rig(
	cflags: []str,
	cc: str,
	flags: []str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	if !no64
	{
		if !no128
		{
			run_config_tests_rig(cflags, cc, flags, usize(64));
		}

		run_config_tests_rig(cflags +~ @[ DEFOPT +~ "BC_RAND_BUILTIN=0" ], cc,
		                     flags, usize(64));

		// Test Editline if history is not turned off.
		if editline && !(flags contains "-Dhistory=none")
		{
			run_config_tests_rig(cflags +~ @[ DEFOPT +~ "BC_RAND_BUILTIN=0" ],
			                     cc, flags +~ @[ "-Dhistory=editline" ],
			                     usize(64));
		}

		// Test Readline if history is not turned off.
		if readline && !(flags contains "-Dhistory=none")
		{
			run_config_tests_rig(cflags +~ @[ DEFOPT +~ "BC_RAND_BUILTIN=0" ],
			                     cc, flags +~ @[ "-Dhistory=readline" ],
			                     usize(64));
		}
	}

	run_config_tests_rig(cflags, cc, flags, usize(32));
}

/**
 * Builds and runs tests with each setting combo running
 * `run_config_series_make()`. If `tests` is false, it just does the builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to `configure.sh` itself.
 */
fn run_settings_series_make(
	cflags: str,
	cc: str,
	flags: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	if settings
	{
		settings_path: str = path.join(SCRIPTDIR, "release_settings_make.txt");
		settings_txt: str = io.read_file(settings_path);
		lines: []str = settings_txt.split("\n");

		for line: lines
		{
			run_config_series_make(cflags, cc, flags +~ " " +~ line);
		}
	}
	else
	{
		run_config_series_make(cflags, cc, flags);
	}
}

/**
 * Builds and runs tests with each setting combo running
 * `run_config_series_rig()`. If `tests` is false, it just does the builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to Rig itself.
 */
fn run_settings_series_rig(
	cflags: []str,
	cc: str,
	flags: []str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	if settings
	{
		settings_path: str = path.join(SCRIPTDIR, "release_settings_rig.txt");
		settings_txt: str = io.read_file(settings_path);
		lines: []str = settings_txt.split("\n");

		for line: lines
		{
			opts_list: []str =
			for opt: line.split(" ")
			{
				DEFOPT +~ opt;
			};

			run_config_series_rig(cflags, cc, flags +~ opts_list);
		}
	}
	else
	{
		run_config_series_rig(cflags, cc, flags);
	}
}

/**
 * Builds and runs tests with each build type running
 * `run_settings_series_make()`. If `tests` is false, it just does the builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to `configure.sh` itself.
 */
fn run_test_series_make(
	cflags: str,
	cc: str,
	flags: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	series_flags: []str = @[ "E", "H", "N", "EH", "EN", "HN", "EHN" ];

	run_settings_series_make(cflags, cc, flags);

	for sflag: series_flags
	{
		run_settings_series_make(cflags, cc, flags +~ " -" +~ sflag);
	}
}

/**
 * Builds and runs tests with each build type running
 * `run_settings_series_rig()`. If `tests` is false, it just does the builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to Rig itself.
 */
fn run_test_series_rig(
	cflags: []str,
	cc: str,
	flags: []str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	series_path: str = path.join(SCRIPTDIR, "release_flags_rig.txt");
	series_txt: str = io.read_file(series_path);
	series_flags: []str = series_txt.split("\n");

	run_settings_series_rig(cflags, cc, flags);

	for line: series_flags
	{
		opts_list: []str =
		for opt: line.split(" ")
		{
			DEFOPT +~ opt;
		};

		run_settings_series_rig(cflags, cc, flags +~ opts_list);
	}
}

/**
 * Builds and runs tests for `bcl` with `make`. If `tests` is false, it just
 * does the builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to `configure.sh` itself.
 */
fn run_lib_tests_make(
	cflags: str,
	cc: str,
	flags: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	build_make(cflags +~ " -a", cc, flags, true, usize(64));

	if tests
	{
		run_test_make(@[ "test" ]);
	}

	build_make(cflags +~ " -a", cc, flags, true, usize(32));

	if tests
	{
		run_test_make(@[ "test" ]);
	}
}

/**
 * Builds and runs tests for `bcl` with Rig. If `tests` is false, it just does
 * the builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to Rig itself.
 */
fn run_lib_tests_rig(
	cflags: []str,
	cc: str,
	flags: []str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	build_rig(cflags, cc, flags +~ @[ "-Dbuild_mode=library" ], usize(64));

	if tests
	{
		run_test_rig(@[ "test" ]);
	}

	build_rig(cflags, cc, flags +~ @[ "-Dbuild_mode=library" ], usize(32));

	if tests
	{
		run_test_rig(@[ "test" ]);
	}
}

/**
 * Builds and runs tests under C99, then C11, if requested, using
 * `run_test_series_make()`. If run_tests is false, it just does the builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to Rig itself.
 */
fn run_tests_make(
	cflags: str,
	cc: str,
	flags: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	run_test_series_make(cflags +~ " " +~ C99OPT, cc, flags);

	if c11
	{
		run_test_series_make(cflags +~ " " +~ C11OPT, cc, flags);
	}
}

/**
 * Builds and runs tests under C99, then C11, if requested, using
 * `run_test_series_rig()`. If run_tests is false, it just does the builds.
 * @param cflags  The C compiler flags.
 * @param cc      The C compiler.
 * @param flags   The flags to pass to Rig itself.
 */
fn run_tests_rig(
	cflags: []str,
	cc: str,
	flags: []str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	run_test_series_rig(cflags +~ @[ C99OPT ], cc, flags);

	if c11
	{
		run_test_series_rig(cflags +~ @[ C11OPT ], cc, flags);
	}
}

/**
 * Runs the Karatsuba tests with `make`.
 */
fn karatsuba_make() -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	header("Running Karatsuba tests");

	do_make(@[ "karatsuba_test" ]);
}

/**
 * Runs the Karatsuba tests with Rig.
 */
fn karatsuba_rig() -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	header("Running Karatsuba tests");

	build_rig(RELEASE_CFLAGS, defcc, @[ "-O3" ], usize(64));

	do_rig(@[ "karatsuba_test" ]);
}

/**
 * Builds with `make` and runs under valgrind. It runs both, bc only, then dc
 * only, then the library.
 */
fn vg_make() -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	header("Running Valgrind under `make`");

	build_make(DEBUG_CFLAGS_STR +~ " -std=c99", "gcc", "-O3 -gv", true,
	           defbits);
	run_test_make(@[ "test" ]);

	do_make(@[ "clean_config" ]);

	build_make(DEBUG_CFLAGS_STR +~ " -std=c99", "gcc", "-O3 -gvb", true,
	           defbits);
	run_test_make(@[ "test" ]);

	do_make(@[ "clean_config" ]);

	build_make(DEBUG_CFLAGS_STR +~ " -std=c99", "gcc", "-O3 -gvd", true,
	           defbits);
	run_test_make(@[ "test" ]);

	do_make(@[ "clean_config" ]);

	build_make(DEBUG_CFLAGS_STR +~ " -std=c99", "gcc", "-O3 -gva", true,
	           defbits);
	run_test_make(@[ "test" ]);

	do_make(@[ "clean_config" ]);
}

fn vg_rig() -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	header("Running Valgrind under Rig");

	build_rig(DEBUG_CFLAGS +~ @[ "-std=c99" ], "gcc", @[ "-O3", "-gv" ],
	          defbits);
	run_test_rig(@[ "test" ]);

	do_rig(@[ "clean_config" ]);

	build_rig(DEBUG_CFLAGS +~ @[ "-std=c99" ], "gcc", @[ "-O3", "-gvb" ],
	          defbits);
	run_test_rig(@[ "test" ]);

	do_rig(@[ "clean_config" ]);

	build_rig(DEBUG_CFLAGS +~ @[ "-std=c99" ], "gcc", @[ "-O3", "-gvd" ],
	          defbits);
	run_test_rig(@[ "test" ]);

	do_rig(@[ "clean_config" ]);

	build_rig(DEBUG_CFLAGS +~ @[ "-std=c99" ], "gcc", @[ "-O3", "-gva" ],
	          defbits);
	run_test_rig(@[ "test" ]);

	do_rig(@[ "clean_config" ]);
}

/**
 * Builds the debug series with `make` and runs the tests if `tests` allows.
 * @param cc  The C compiler.
 */
fn debug_make(
	cc: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	if cc == "clang" && sanitizers
	{
		run_tests_make(DEBUG_CFLAGS_STR +~ " -fsanitize=undefined", cc, "-mg");
	}
	else
	{
		run_tests_make(DEBUG_CFLAGS_STR, cc, "-g");
	}
}

/**
 * Builds the release series with `make` and runs the tests if `tests` allows.
 * @param cc  The C compiler.
 */
fn release_make(
	cc: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	run_tests_make(RELEASE_CFLAGS_STR, cc, "-O3");
	run_lib_tests_make(RELEASE_CFLAGS_STR, cc, "-O3");
}

/**
 * Builds the release debug series with `make` and runs the tests if `tests`
 * allows.
 * @param cc  The C compiler.
 */
fn reldebug_make(
	cc: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	if cc == "clang" && sanitizers
	{
		run_tests_make(DEBUG_CFLAGS_STR +~ " -fsanitize=address", cc, "-mgO3");
		run_tests_make(DEBUG_CFLAGS_STR +~ " -fsanitize=memory", cc, "-mgO3");
	}
	else
	{
		run_tests_make(DEBUG_CFLAGS_STR, cc, "-gO3");
	}

	if cc == "clang" && sanitizers
	{
		run_lib_tests_make(DEBUG_CFLAGS_STR +~ " -fsanitize=address", cc,
		                   "-mgO3");
		run_lib_tests_make(DEBUG_CFLAGS_STR +~ " -fsanitize=memory", cc,
		                   "-mgO3");
	}
	else
	{
		run_lib_tests_make(DEBUG_CFLAGS_STR, cc, "-gO3");
	}
}

/**
 * Builds the min size release with `make` and runs the tests if `tests` allows.
 * @param cc  The C compiler.
 */
fn minsize_make(
	cc: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	run_tests_make(RELEASE_CFLAGS_STR, cc, "-Os");
	run_lib_tests_make(RELEASE_CFLAGS_STR, cc, "-Os");
}

/**
 * Builds all sets with `make`: debug, release, release debug, and min size, and
 * runs the tests if `tests` allows.
 * @param cc  The C compiler.
 */
fn build_set_make(
	cc: str,
) -> void
{
	if !run_make
	{
		sys.panic("Cannot run `configure.sh` or make");
	}

	debug_make(cc);
	release_make(cc);
	reldebug_make(cc);
	minsize_make(cc);

	if karatsuba
	{
		karatsuba_make();
	}

	if valgrind
	{
		vg_make();
	}
}

/**
 * Builds the debug series with Rig and runs the tests if `tests` allows.
 * @param cc  The C compiler.
 */
fn debug_rig(
	cc: str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	if cc == "clang" && sanitizers
	{
		run_tests_rig(DEBUG_CFLAGS +~ @[ " -fsanitize=undefined" ], cc,
		              @[ "-Dmemcheck=1", "-Ddebug=1" ]);
	}
	else
	{
		run_tests_rig(DEBUG_CFLAGS, cc, @[ "-Ddebug=1" ]);
	}
}

/**
 * Builds the release series with Rig and runs the tests if `tests` allows.
 * @param cc  The C compiler.
 */
fn release_rig(
	cc: str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	run_tests_rig(RELEASE_CFLAGS, cc, @[ "-Doptimization=3" ]);
	run_lib_tests_rig(RELEASE_CFLAGS, cc, @[ "-Doptimization=3" ]);
}

/**
 * Builds the release debug series with Rig and runs the tests if `tests`
 * allows.
 * @param cc  The C compiler.
 */
fn reldebug_rig(
	cc: str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	if cc == "clang" && sanitizers
	{
		run_tests_rig(DEBUG_CFLAGS +~ @[ " -fsanitize=address" ], cc,
		              @[ "-Dmemcheck=1", "-Ddebug=1", "-Doptimization=3" ]);
		run_tests_rig(DEBUG_CFLAGS +~ @[ " -fsanitize=memory" ], cc,
		              @[ "-Dmemcheck=1", "-Ddebug=1", "-Doptimization=3" ]);
	}
	else
	{
		run_tests_rig(DEBUG_CFLAGS, cc, @[ "-gO3" ]);
	}

	if cc == "clang" && sanitizers
	{
		run_lib_tests_rig(DEBUG_CFLAGS +~ @[ " -fsanitize=address" ], cc,
		                  @[ "-Dmemcheck=1", "-Ddebug=1", "-Doptimization=3" ]);
		run_lib_tests_rig(DEBUG_CFLAGS +~ @[ " -fsanitize=memory" ], cc,
		                  @[ "-Dmemcheck=1", "-Ddebug=1", "-Doptimization=3" ]);
	}
	else
	{
		run_lib_tests_rig(DEBUG_CFLAGS, cc,
		                  @[ "-Ddebug=1", "-Doptimization=3" ]);
	}
}

/**
 * Builds the min size release with Rig and runs the tests if `tests` allows.
 * @param cc  The C compiler.
 */
fn minsize_rig(
	cc: str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	run_tests_rig(RELEASE_CFLAGS, cc, @[ "-Doptimization=s" ]);
	run_lib_tests_rig(RELEASE_CFLAGS, cc, @[ "-Doptimization=s" ]);
}

/**
 * Builds all sets with Rig: debug, release, release debug, and min size, and
 * runs the tests if `tests` allows.
 * @param cc  The C compiler.
 */
fn build_set_rig(
	cc: str,
) -> void
{
	if !run_rig
	{
		sys.panic("Cannot run Rig");
	}

	debug_rig(cc);
	release_rig(cc);
	reldebug_rig(cc);
	minsize_rig(cc);

	if karatsuba
	{
		karatsuba_rig();
	}

	if valgrind
	{
		vg_rig();
	}
}

/**
 * Builds all sets with `make` under all compilers.
 */
fn build_sets_make() -> void
{
	header("Running math library under --standard with Rig");

	build_make(DEBUG_CFLAGS_STR +~ " -std=c99", defcc, "-g", true,
	           defbits);

	$ bin/bc -ls << @("quit\n");

	do_rig(@[ "clean_tests" ]);

	if clang
	{
		build_set_make("clang");
	}

	if gcc
	{
		build_set_make("gcc");
	}
}

/**
 * Builds all sets with Rig under all compilers.
 */
fn build_sets_rig() -> void
{
	header("Running math library under --standard with Rig");

	build_rig(DEBUG_CFLAGS +~ @[ "-std=c99" ], defcc, @[ "-Ddebug=1" ],
	          defbits);

	$ build/bc -ls << @("quit\n");

	do_rig(@[ "clean_tests" ]);

	if clang
	{
		build_set_rig("clang");
	}

	if gcc
	{
		build_set_rig("gcc");
	}
}

fn build_sets() -> void
{
	if !run_make
	{
		build_sets_rig();
	}
	else if !run_rig
	{
		build_sets_make();
	}
	else
	{
		parallel.map @[ "rig", "make" ]:
		(builder: str) -> void
		{
			if builder == "rig"
			{
				build_sets_rig();
			}
			else if builder == "make"
			{
				build_sets_make();
			}
			else
			{
				sys.panic("Bad builder: " +~ builder +~ "\n");
			}
		}
	}

	io.eprint("\nTests successful.\n");
}