Ctrl K
ComunidadGuías…

ci/linux_ci.vsh

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import common { Task, exec_args }
import crypto.sha256
import os

// Shared tasks/helpers
fn all_code_is_formatted() {
	if common.is_github_job {
		exec_args(['v', '-silent', 'test-cleancode'])
	} else {
		exec_args(['v', '-progress', 'test-cleancode'])
	}
}

fn verify_v_test_works() {
	println(os.getenv('VFLAGS'))
	exec_args(['v', 'cmd/tools/test_if_v_test_system_works.v'])
	exec_args(['./cmd/tools/test_if_v_test_system_works'])
}

fn test_pure_v_math_module() {
	exec_args(['v', '-d', 'pure_v_math', '-exclude', '@vlib/math/*.c.v', 'test', 'vlib/math/'])
}

fn self_tests() {
	// Never select the V1 compatibility compiler here. It is a separate V 0.5.2
	// installation, so `test-self vlib` would resolve `vlib` under *its* VROOT and
	// test the release's own standard library instead of this repository's.
	if common.is_github_job {
		exec_args(['v', '-W', '-silent', 'test-self', 'vlib'])
	} else {
		exec_args(['v', '-progress', 'test-self', 'vlib'])
	}
}

fn build_examples() {
	if common.is_github_job {
		// The exhaustive CI matrix includes a few deliberately large examples.
		// Keep the user-facing V3 memory guard while allowing CI to validate them.
		exec_args(['v', '-no-memory-limit', '-W', 'build-examples'])
	} else {
		exec_args(['v', '-progress', 'build-examples'])
	}
}

fn v_doctor() {
	exec_args(['v', 'doctor'])
}

fn build_v_with_prealloc() {
	exec_args(['v', '-cg', '-cstrict', '-o', 'vstrict1', 'cmd/v'])
	// -prealloc uses _Thread_local for g_memory_block; bundled tcc does not support it.
	prealloc_cc_flag := if os.getenv('VFLAGS').contains('-cc tcc') { ' -cc cc' } else { '' }
	exec_args(['./vstrict1', ...(os.split_args(prealloc_cc_flag) or { panic(err) }), '-o', 'vprealloc',
		'-prealloc', 'cmd/v'])
	exec_args(['./vprealloc', 'run', 'examples/hello_world.v'])
	exec_args(['./vprealloc', '-o', 'v3', 'cmd/v'])
	exec_args(['./v3', '-o', 'v4', 'cmd/v'])
	exec_args(['./v4', '-d', 'debug_malloc', '-d', 'debug_realloc', '-o', 'vdebug1', 'cmd/v'])
}

// TCC job tasks
fn install_dependencies_for_examples_and_tools_tcc() {
	if common.is_github_job && os.exists('/etc/apt/apt-mirrors.txt') {
		exec_args(['.github/workflows/disable_azure_mirror.sh'])
	}
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'update'])
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'libssl-dev', 'sqlite3',
		'libsqlite3-dev', 'valgrind', 'nodejs'])
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'libfreetype6-dev',
		'libxi-dev', 'libxcursor-dev', 'libgl-dev', 'libxrandr-dev', 'libasound2-dev', 'libegl-dev',
		'libx11-xcb-dev'])
	// Wayland development libraries for sokol Wayland support
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'libwayland-dev',
		'libxkbcommon-dev', 'libwayland-egl1', 'libxkbcommon-x11-dev'])
	// The following is needed for examples/c_interop_wkhtmltopdf.v.
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'xfonts-75dpi',
		'xfonts-base', 'expect'])
	exec_args(['v', 'retry', '--', 'sh', 'ci/install_wkhtmltox.sh'])
}

fn test_v_to_c_tcc() {
	exec_args(['thirdparty/tcc/tcc.exe', '-version'])
	exec_args(['v', '-cg', '-o', 'vtcc', 'cmd/v']) // ensure vtcc can build itself twice
}

fn v_self_compilation_tcc() {
	exec_args(['v', '-o', 'v2', 'cmd/v'])
	exec_args(['./v2', '-o', 'v3', 'cmd/v'])
	exec_args(['./v3', '-o', 'v4', 'cmd/v'])
}

fn v_doctor_tcc() {
	v_doctor()
}

fn verify_v_test_works_tcc() {
	verify_v_test_works()
}

fn test_pure_v_math_module_tcc() {
	test_pure_v_math_module()
}

fn self_tests_tcc() {
	exec_args(['v', '-keepc', '-cc', 'tcc', '-g', 'self'])
	self_tests()
}

fn self_tests_vprod_tcc() {
	// Keep the optimized test compiler while still checking a TCC debug self-build.
	exec_args(['v', '-keepc', '-cc', 'tcc', '-g', '-o', 'v_tcc_debug', 'self'])
	exec_args(['./v_tcc_debug', 'run', 'examples/hello_world.v'])
	// Emit a separate result line for every file instead of hiding successes.
	exec_args(['v', '-W', 'test-self', 'vlib'])
}

fn build_examples_tcc() {
	build_examples()
}

fn run_submodule_example_tcc() {
	exec_args(['v', '-W', 'run', 'examples/submodule'])
}

fn build_tools_tcc() {
	if common.is_github_job {
		exec_args(['v', '-N', '-W', 'build-tools'])
	} else {
		exec_args(['v', '-progress', '-N', '-W', 'build-tools'])
	}
}

fn build_vbinaries_tcc() {
	exec_args(['v', '-N', '-W', 'build-vbinaries'])
}

fn build_benches_tcc() {
	exec_args(['v', 'should-compile-all', 'vlib/v/tests/bench/'])
}

fn run_vsh_script_tcc() {
	exec_args(['v', 'run', 'examples/v_script.vsh'])
}

fn test_v_tutorials_tcc() {
	exec_args(['v', 'tutorials/building_a_simple_web_blog_with_veb/code/blog'])
}

fn build_fast_tcc() {
	os.chdir('cmd/tools/fast') or { panic(err) }
	exec_args(['v', '-o', 'fast', '.'])
	os.chdir('../../..') or { panic(err) }
	os.chdir('cmd/tools/fast') or { panic(err) }
	exec_args(['./fast', 'help'])
	os.chdir('../../..') or { panic(err) }
}

fn v_self_compilation_usecache_tcc() {
	$if !enable_usecache_test ? {
		eprintln('> ${@LOCATION} use `-d enable_usecache_test` in VFLAGS to enable this task')
		return
	}
	exec_args(['v', 'wipe-cache'])
	exec_args(['v', 'examples/hello_world.v'])
	exec_args(['./examples/hello_world'])

	exec_args(['v', 'wipe-cache'])
	exec_args(['v', '-o', 'v2', 'cmd/v'])
	exec_args(['./v2', '-o', 'v3', 'cmd/v'])
	exec_args(['./v3', 'version'])

	exec_args(['v', 'wipe-cache'])
	exec_args(['./v3', '-o', 'tetris', 'examples/tetris/tetris.v'])
	exec_args(['rm', '-f', './examples/hello_world', 'v2', 'v3', 'tetris'])
}

fn test_password_input_tcc() {
	exec_args(['v', 'test', 'examples/password/'])
}

fn test_readline_tcc() {
	exec_args(['v', 'test', 'examples/readline/'])
}

fn test_leak_detector_tcc() {
	exec_args(['v', '-gc', 'boehm_leak', '-o', 'testcase_leak', 'vlib/v/tests/testcase_leak.vv'])
	exec_args(['sh', '-c', './testcase_leak 2>leaks.txt'])
	exec_args(['grep', 'Found 1 leaked object', 'leaks.txt'])
	exec_args(['grep', '-P', ', sz=\\s?1000,', 'leaks.txt'])
}

fn test_leak_detector_not_active_tcc() {
	exec_args(['v', '-o', 'testcase_leak', 'vlib/v/tests/testcase_leak.vv'])
	exec_args(['sh', '-c', './testcase_leak 2>leaks.txt'])
	assert os.file_size('leaks.txt') == 0, os.read_file('leaks.txt') or { err.msg() }
}

// GCC job tasks
fn all_code_is_formatted_gcc() {
	all_code_is_formatted()
}

fn install_dependencies_for_examples_and_tools_gcc() {
	if common.is_github_job && os.exists('/etc/apt/apt-mirrors.txt') {
		exec_args(['.github/workflows/disable_azure_mirror.sh'])
	}
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'update'])
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'postgresql', 'libpq-dev',
		'libssl-dev', 'sqlite3', 'libsqlite3-dev', 'valgrind', 'nodejs'])
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'libfreetype6-dev',
		'libxi-dev', 'libxcursor-dev', 'libgl-dev', 'libxrandr-dev', 'libasound2-dev'])
	// Wayland development libraries for sokol Wayland support
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'libwayland-dev',
		'libxkbcommon-dev', 'libwayland-egl1', 'libxkbcommon-x11-dev', 'wayland-protocols', 'libegl-dev'])
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'libx11-dev',
		'libx11-xcb-dev', 'libgl1-mesa-dri', 'xauth', 'xvfb'])
}

fn recompile_v_with_cstrict_gcc() {
	exec_args(['v', '-cc', 'gcc', '-cg', '-cstrict', '-o', 'vstrict', 'cmd/v'])
}

fn valgrind_v_c_gcc() {
	exec_args(['valgrind', '--error-exitcode=1', 'v', '-o', 'v.c', 'cmd/v'])
}

fn run_sanitizers_gcc() {
	exec_args(['v', '-o', 'v2', 'cmd/v', '-cflags', '-fsanitize=thread'])
	exec_args(['v', '-o', 'v3', 'cmd/v', '-cflags', '-fsanitize=undefined -fno-sanitize=alignment'])
	exec_args(['env', 'UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1', './v2', '-o', 'v.c',
		'cmd/v'])
	exec_args(['env', 'UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1', './v3', '-o', 'v.c',
		'cmd/v'])
}

fn v_self_compilation_gcc() {
	exec_args(['v', '-o', 'v2', 'cmd/v'])
	exec_args(['./v2', '-o', 'v3', 'cmd/v'])
	exec_args(['./v3', '-o', 'v4', 'cmd/v'])
}

fn v_self_compilation_usecache_gcc() {
	$if !enable_usecache_test ? {
		eprintln('> ${@LOCATION} use `-d enable_usecache_test` in VFLAGS to enable this task')
		return
	}
	exec_args(['v', 'wipe-cache'])
	exec_args(['v', 'examples/hello_world.v'])
	exec_args(['examples/hello_world'])

	exec_args(['v', 'wipe-cache'])
	exec_args(['v', '-o', 'v2', 'cmd/v'])
	exec_args(['./v2', '-o', 'v3', 'cmd/v'])
	exec_args(['./v3', 'version'])

	exec_args(['v', 'wipe-cache'])
	exec_args(['./v3', '-o', 'tetris', 'examples/tetris/tetris.v'])
	exec_args(['rm', '-f', './examples/hello_world', 'v2', 'v3', 'tetris'])
}

fn verify_v_test_works_gcc() {
	verify_v_test_works()
}

fn test_pure_v_math_module_gcc() {
	test_pure_v_math_module()
}

fn self_tests_gcc() {
	self_tests()
}

fn self_tests_prod_gcc() {
	exec_args(['v', '-o', 'vprod', '-prod', 'cmd/v'])
	exec_args(['./vprod', '-silent', 'test-self', 'vlib'])
}

fn self_tests_cstrict_gcc() {
	exec_args(['env', 'VTEST_JUST_ESSENTIAL=1', 'V_CI_CSTRICT=1', 'v', '-cc', 'gcc', '-cstrict',
		'-silent', 'test-self', 'vlib'])
}

fn skip_ownership_autofree_test() bool {
	return common.is_github_job || os.getenv('VTEST_SKIP_OWNERSHIP') == '1'
}

fn report_skipped_ownership_autofree_test() {
	eprintln('> skipping ownership/autofree test')
}

fn build_examples_gcc() {
	build_examples()
}

fn build_tetris_autofree_gcc() {
	if skip_ownership_autofree_test() {
		report_skipped_ownership_autofree_test()
		return
	}
	exec_args(['v', '-autofree', '-o', 'tetris', 'examples/tetris/tetris.v'])
	exec_args(['rm', '-f', 'tetris'])
}

fn build_blog_autofree_gcc() {
	if skip_ownership_autofree_test() {
		report_skipped_ownership_autofree_test()
		return
	}
	exec_args(['v', '-autofree', '-o', 'blog', 'tutorials/building_a_simple_web_blog_with_veb/code/blog'])
	exec_args(['rm', '-f', 'blog'])
}

fn build_option_test_autofree_gcc() {
	if skip_ownership_autofree_test() {
		report_skipped_ownership_autofree_test()
		return
	}
	exec_args(['v', '-autofree', 'vlib/v/tests/options/option_test.c.v'])
}

fn v_self_compilation_parallel_cc_gcc() {
	exec_args(['v', '-o', 'v2', '-parallel-cc', 'cmd/v'])
	exec_args(['rm', '-f', 'v2'])
}

fn build_modules_gcc() {
	exec_args(['v', 'build-module', 'vlib/os'])
	exec_args(['v', 'build-module', 'vlib/builtin'])
	exec_args(['v', 'build-module', 'vlib/strconv'])
	exec_args(['v', 'build-module', 'vlib/time'])
	exec_args(['v', 'build-module', 'vlib/term'])
	exec_args(['v', 'build-module', 'vlib/math'])
	exec_args(['v', 'build-module', 'vlib/strings'])
	exec_args(['v', 'build-module', 'vlib/v/token'])
	exec_args(['v', 'build-module', 'vlib/v/parser'])
	exec_args(['v', 'build-module', 'vlib/v/gen/c'])
	exec_args(['v', 'build-module', 'vlib/os/cmdline'])
}

fn compile_vdoctor_prod_gcc() {
	exec_args(['v', '-showcc', '-cc', 'gcc', '-prod', 'cmd/tools/vdoctor.v'])
}

fn compile_vup_prod_gcc() {
	exec_args(['v', '-showcc', '-cc', 'gcc', '-prod', 'cmd/tools/vup.v'])
}

// Clang job tasks
fn all_code_is_formatted_clang() {
	all_code_is_formatted()
}

fn install_dependencies_for_examples_and_tools_clang() {
	if common.is_github_job && os.exists('/etc/apt/apt-mirrors.txt') {
		exec_args(['.github/workflows/disable_azure_mirror.sh'])
	}
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'update'])
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'postgresql', 'libpq-dev',
		'libssl-dev', 'sqlite3', 'libsqlite3-dev', 'valgrind', 'nodejs'])
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'libfreetype6-dev',
		'libxi-dev', 'libxcursor-dev', 'libgl-dev', 'libxrandr-dev', 'libasound2-dev'])
	// Wayland development libraries for sokol Wayland support
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'libwayland-dev',
		'libxkbcommon-dev', 'libwayland-egl1', 'libxkbcommon-x11-dev', 'wayland-protocols', 'libegl-dev'])
	exec_args(['v', 'retry', '--', 'sudo', 'apt', 'install', '--quiet', '-y', 'clang', 'libx11-xcb-dev'])
}

fn recompile_v_with_cstrict_clang() {
	exec_args(['v', '-cc', 'clang', '-cg', '-cstrict', '-o', 'vstrict', 'cmd/v'])
	exec_args(['rm', '-f', 'vstrict'])
}

fn valgrind_clang() {
	exec_args(['valgrind', '--error-exitcode=1', 'v', '-o', 'v.c', 'cmd/v'])
	exec_args(['rm', '-f', 'v.c'])
}

fn run_sanitizers_clang() {
	exec_args(['.github/workflows/run_sanitizers.sh'])
}

fn v_self_compilation_clang() {
	exec_args(['v', '-o', 'v2', 'cmd/v'])
	exec_args(['./v2', '-o', 'v3', 'cmd/v'])
	exec_args(['./v3', '-o', 'v4', 'cmd/v'])
}

fn v_self_compilation_usecache_clang() {
	$if !enable_usecache_test ? {
		eprintln('> ${@LOCATION} use `-d enable_usecache_test` in VFLAGS to enable this task')
		return
	}
	exec_args(['v', 'wipe-cache'])
	exec_args(['v', 'examples/hello_world.v'])
	exec_args(['./examples/hello_world'])

	exec_args(['v', 'wipe-cache'])
	exec_args(['v', '-o', 'v2', 'cmd/v'])
	exec_args(['./v2', '-o', 'v3', 'cmd/v'])
	exec_args(['./v3', 'version'])

	exec_args(['v', 'wipe-cache'])
	exec_args(['./v3', '-o', 'tetris', 'examples/tetris/tetris.v'])
	exec_args(['rm', '-f', './examples/hello_world', 'v2', 'v3', 'tetris'])
}

fn verify_v_test_works_clang() {
	verify_v_test_works()
}

fn test_pure_v_math_module_clang() {
	test_pure_v_math_module()
}

fn self_tests_clang() {
	self_tests()
}

fn self_tests_vprod_clang() {
	exec_args(['v', '-o', 'vprod', '-prod', 'cmd/v'])
	exec_args(['./vprod', '-silent', 'test-self', 'vlib'])
}

fn self_tests_cstrict_clang() {
	exec_args(['env', 'VTEST_JUST_ESSENTIAL=1', 'V_CI_CSTRICT=1', './vprod', '-cstrict', '-silent',
		'test-self', 'vlib'])
}

fn build_examples_clang() {
	build_examples()
}

fn build_examples_autofree_clang() {
	if skip_ownership_autofree_test() {
		report_skipped_ownership_autofree_test()
		return
	}
	exec_args(['v', '-N', '-W', '-autofree', '-experimental', '-o', 'tetris', 'examples/tetris/tetris.v'])
	exec_args(['rm', '-f', 'tetris'])
}

fn build_modules_clang() {
	exec_args(['v', 'build-module', 'vlib/os'])
	exec_args(['v', 'build-module', 'vlib/builtin'])
	exec_args(['v', 'build-module', 'vlib/strconv'])
	exec_args(['v', 'build-module', 'vlib/time'])
	exec_args(['v', 'build-module', 'vlib/term'])
	exec_args(['v', 'build-module', 'vlib/math'])
	exec_args(['v', 'build-module', 'vlib/strings'])
	exec_args(['v', 'build-module', 'vlib/v/token'])
	exec_args(['v', 'build-module', 'vlib/v/parser'])
	exec_args(['v', 'build-module', 'vlib/v/gen/c'])
	exec_args(['v', 'build-module', 'vlib/os/cmdline'])
}

fn test_inline_assembly() {
	exec_args(['v', 'test', 'vlib/v/slow_tests/assembly'])
}

// Keep this list in the same order as the active tasks in
// .github/workflows/linux_ci.yml. The aggregate runner changes compiler flags at
// the same boundaries as the three workflow jobs.
// Order is deliberate on both sides: the cheapest check that can prove a checkout
// broken runs first, and `build_v_with_prealloc`, which is five compiler builds,
// runs last so a red run reports the cheap signal before paying for it.
const ci_tasks = [
	'all_code_is_formatted_tcc',
	'install_dependencies_for_examples_and_tools_tcc',
	'test_v_to_c_tcc',
	'v_self_compilation_tcc',
	'v_doctor_tcc',
	'verify_v_test_works_tcc',
	'test_pure_v_math_module_tcc',
	'test_inline_assembly',
	'self_tests_vprod_tcc',
	'build_examples_tcc',
	'run_submodule_example_tcc',
	'build_tools_tcc',
	'build_vbinaries_tcc',
	'build_benches_tcc',
	'run_vsh_script_tcc',
	'test_v_tutorials_tcc',
	'build_fast_tcc',
	'v_self_compilation_usecache_tcc',
	'test_password_input_tcc',
	'test_readline_tcc',
	'test_leak_detector_tcc',
	'test_leak_detector_not_active_tcc',
	'build_v_with_prealloc',
	'all_code_is_formatted_gcc',
	'install_dependencies_for_examples_and_tools_gcc',
	'recompile_v_with_cstrict_gcc',
	'valgrind_v_c_gcc',
	'run_sanitizers_gcc',
	'v_self_compilation_gcc',
	'v_self_compilation_usecache_gcc',
	'verify_v_test_works_gcc',
	'test_pure_v_math_module_gcc',
	'self_tests_gcc',
	'self_tests_prod_gcc',
	'self_tests_cstrict_gcc',
	'build_examples_gcc',
	'build_tetris_autofree_gcc',
	'build_blog_autofree_gcc',
	'build_option_test_autofree_gcc',
	'v_self_compilation_parallel_cc_gcc',
	'build_modules_gcc',
	'compile_vdoctor_prod_gcc',
	'compile_vup_prod_gcc',
	'all_code_is_formatted_clang',
	'install_dependencies_for_examples_and_tools_clang',
	'recompile_v_with_cstrict_clang',
	'valgrind_clang',
	'run_sanitizers_clang',
	'v_self_compilation_clang',
	'v_self_compilation_usecache_clang',
	'verify_v_test_works_clang',
	'test_pure_v_math_module_clang',
	'self_tests_clang',
	'self_tests_vprod_clang',
	'self_tests_cstrict_clang',
	'build_examples_clang',
	'build_examples_autofree_clang',
	'build_modules_clang',
]

// Keep progress across edits/rebuilds, but isolate users and checkout directories.
fn ci_progress_path() string {
	checkout := sha256.hexhash(os.real_path(os.getwd()))
	return os.join_path(os.cache_dir(), 'v-linux-ci-${os.getuid()}-${checkout}.progress')
}

fn ci_progress_contents(task_name string) string {
	// Record the whole ordered task list so changed plans restart safely.
	return 'linux-ci-v1\n${task_name}\n${ci_tasks.join('\n')}\n'
}

fn ci_resume_index(path string) !int {
	if !os.exists(path) {
		return -1
	}
	if !os.is_file(path) {
		return error('CI progress path is not a file: ${path}')
	}
	saved := os.read_file(path)!
	for i, task_name in ci_tasks {
		if saved == ci_progress_contents(task_name) {
			return i
		}
	}
	eprintln('Ignoring invalid or outdated CI progress; restarting from the first task.')
	return -1
}

fn save_ci_progress(path string, task_name string) ! {
	// Write privately, then rename on the same filesystem. An interrupted write
	// leaves the previous checkpoint intact, never a partially written cursor.
	if os.exists(path) && !os.is_file(path) {
		return error('CI progress path is not a file: ${path}')
	}
	tmp_dir := '${path}.${os.getpid()}.tmp'
	os.mkdir(tmp_dir, mode: 0o700)!
	defer {
		os.rmdir_all(tmp_dir) or {}
	}
	tmp_path := os.join_path(tmp_dir, 'progress')
	os.write_file(tmp_path, ci_progress_contents(task_name))!
	os.rename(tmp_path, path)!
}

fn ci_vflags(task_index int) string {
	if task_index < ci_tasks.index('all_code_is_formatted_gcc') {
		return '-cc tcc -no-retry-compilation'
	}
	if task_index < ci_tasks.index('all_code_is_formatted_clang') {
		return '-cc gcc'
	}
	return '-cc clang'
}

// run_ci_tasks mirrors the active ci/linux_ci.vsh steps in
// .github/workflows/linux_ci.yml. The generic `all` mode intentionally remains
// exhaustive, including tasks that are currently disabled in the workflow.
fn run_ci_tasks(reset bool) ! {
	// Match the GitHub Actions environment that changes test behavior.
	os.setenv('CI', 'true', true)
	os.setenv('GITHUB_ACTIONS', 'true', true)
	os.setenv('RUNNER_OS', 'Linux', true)
	os.setenv('V_MACOS_V3_NO_FALLBACK', '1', true)
	// Stop test/build sessions promptly while retaining Linux CI's normal worker count.
	os.setenv('VTEST_FAIL_FAST', '1', true)
	os.setenv('VTEST_SHOW_LONGEST_BY_RUNTIME', '3', true)
	os.setenv('VTEST_SHOW_LONGEST_BY_COMPTIME', '3', true)
	os.setenv('VTEST_SHOW_LONGEST_BY_TOTALTIME', '3', true)
	os.setenv('VTEST_SKIP_OWNERSHIP', '1', true)

	progress_path := ci_progress_path()
	progress_dir := '${progress_path}.d'
	saved_index := if reset { -1 } else { ci_resume_index(progress_path)! }
	// No valid cursor means none of its finer-grained records may be reused.
	if saved_index < 0 && os.exists(progress_dir) {
		os.rmdir_all(progress_dir)!
	}
	if !os.exists(progress_dir) {
		os.mkdir(progress_dir, mode: 0o700)!
	}
	start := if saved_index < 0 { 0 } else { saved_index }
	os.unsetenv('VTEST_RESUME_OWNER')
	eprintln('CI progress: ${progress_path}')
	eprintln('Use `v run ci/linux_ci.vsh ci --reset` to restart from the first task.')
	if start > 0 {
		eprintln('Resuming at ${ci_tasks[start]}; skipping ${start} completed CI tasks.')
	}
	for i in start .. ci_tasks.len {
		task_name := ci_tasks[i]
		// Save BEFORE execution: a failure or interruption must retry this task.
		save_ci_progress(progress_path, task_name)!
		os.setenv('V_CI_TASK_PROGRESS', os.join_path(progress_dir, task_name), true)
		os.setenv('VFLAGS', ci_vflags(i), true)
		if task_name.ends_with('_tcc') || task_name in ['build_v_with_prealloc', 'test_inline_assembly'] {
			os.setenv('GITHUB_JOB', 'tcc-linux', true)
		} else if task_name.ends_with('_gcc') {
			os.setenv('GITHUB_JOB', 'gcc-linux', true)
		} else {
			os.setenv('GITHUB_JOB', 'clang-linux', true)
		}
		eprintln('CI task ${i + 1}/${ci_tasks.len}: ${task_name}')
		exec_args(['v', 'run', 'ci/linux_ci.vsh', '${task_name}'])
	}
	os.rmdir_all(progress_dir)!
	os.rm(progress_path)!
	eprintln('CI tasks complete; progress cleared.')
}

// Collect all tasks
const all_tasks = {
	'build_v_with_prealloc':                             Task{build_v_with_prealloc, 'Build V with prealloc'}
	// tcc tasks
	'all_code_is_formatted_tcc':                         Task{all_code_is_formatted, 'All code is formatted (tcc)'}
	'install_dependencies_for_examples_and_tools_tcc':   Task{install_dependencies_for_examples_and_tools_tcc, 'Install deps for examples/tools (tcc)'}
	'test_v_to_c_tcc':                                   Task{test_v_to_c_tcc, 'Test v->c with tcc'}
	'v_self_compilation_tcc':                            Task{v_self_compilation_tcc, 'V self compilation (tcc)'}
	'v_doctor_tcc':                                      Task{v_doctor_tcc, 'v doctor (tcc)'}
	'verify_v_test_works_tcc':                           Task{verify_v_test_works_tcc, 'Verify `v test` works (tcc)'}
	'test_pure_v_math_module_tcc':                       Task{test_pure_v_math_module_tcc, 'Test pure V math module (tcc)'}
	'self_tests_tcc':                                    Task{self_tests_tcc, 'Self tests (tcc)'}
	'self_tests_vprod_tcc':                              Task{self_tests_vprod_tcc, 'Self tests using the optimized compiler (tcc)'}
	'build_examples_tcc':                                Task{build_examples_tcc, 'Build examples (tcc)'}
	'run_submodule_example_tcc':                         Task{run_submodule_example_tcc, 'Run submodule example (tcc)'}
	'build_tools_tcc':                                   Task{build_tools_tcc, 'Build V tools (tcc)'}
	'build_vbinaries_tcc':                               Task{build_vbinaries_tcc, 'Build V binaries (tcc)'}
	'build_benches_tcc':                                 Task{build_benches_tcc, 'Build benches (tcc)'}
	'run_vsh_script_tcc':                                Task{run_vsh_script_tcc, 'Run a VSH script (tcc)'}
	'test_v_tutorials_tcc':                              Task{test_v_tutorials_tcc, 'Test V tutorials (tcc)'}
	'build_fast_tcc':                                    Task{build_fast_tcc, 'Build cmd/tools/fast (tcc)'}
	'v_self_compilation_usecache_tcc':                   Task{v_self_compilation_usecache_tcc, 'V self compilation with default caching (tcc)'}
	'test_password_input_tcc':                           Task{test_password_input_tcc, 'Test password input (tcc)'}
	'test_readline_tcc':                                 Task{test_readline_tcc, 'Test readline (tcc)'}
	'test_leak_detector_tcc':                            Task{test_leak_detector_tcc, 'Test leak detector (tcc)'}
	'test_leak_detector_not_active_tcc':                 Task{test_leak_detector_not_active_tcc, 'Test leak detector not active (tcc)'}
	// gcc tasks
	'all_code_is_formatted_gcc':                         Task{all_code_is_formatted_gcc, 'All code is formatted (gcc)'}
	'install_dependencies_for_examples_and_tools_gcc':   Task{install_dependencies_for_examples_and_tools_gcc, 'Install deps for examples/tools (gcc)'}
	'recompile_v_with_cstrict_gcc':                      Task{recompile_v_with_cstrict_gcc, 'Recompile V with -cstrict and gcc'}
	'valgrind_v_c_gcc':                                  Task{valgrind_v_c_gcc, 'Valgrind v.c (gcc)'}
	'run_sanitizers_gcc':                                Task{run_sanitizers_gcc, 'Run sanitizers (gcc)'}
	'v_self_compilation_gcc':                            Task{v_self_compilation_gcc, 'V self compilation (gcc)'}
	'v_self_compilation_usecache_gcc':                   Task{v_self_compilation_usecache_gcc, 'V self compilation with default caching (gcc)'}
	'verify_v_test_works_gcc':                           Task{verify_v_test_works_gcc, 'Verify `v test` works (gcc)'}
	'test_pure_v_math_module_gcc':                       Task{test_pure_v_math_module_gcc, 'Test pure V math module (gcc)'}
	'self_tests_gcc':                                    Task{self_tests_gcc, 'Self tests (gcc)'}
	'self_tests_prod_gcc':                               Task{self_tests_prod_gcc, 'Self tests (-prod) (gcc)'}
	'self_tests_cstrict_gcc':                            Task{self_tests_cstrict_gcc, 'Self tests (-cstrict) (gcc)'}
	'build_examples_gcc':                                Task{build_examples_gcc, 'Build examples (gcc)'}
	'build_tetris_autofree_gcc':                         Task{build_tetris_autofree_gcc, 'Build tetris with -autofree (gcc)'}
	'build_blog_autofree_gcc':                           Task{build_blog_autofree_gcc, 'Build blog tutorial with -autofree (gcc)'}
	'build_option_test_autofree_gcc':                    Task{build_option_test_autofree_gcc, 'Build option_test.c.v with -autofree (gcc)'}
	'v_self_compilation_parallel_cc_gcc':                Task{v_self_compilation_parallel_cc_gcc, 'V self compilation with -parallel-cc (gcc)'}
	'build_modules_gcc':                                 Task{build_modules_gcc, 'Build modules (gcc)'}
	'compile_vdoctor_prod_gcc':                          Task{compile_vdoctor_prod_gcc, 'compile vdoctor with -prod (gcc)'}
	'compile_vup_prod_gcc':                              Task{compile_vup_prod_gcc, 'compile vup with -prod (gcc)'}
	// clang tasks
	'all_code_is_formatted_clang':                       Task{all_code_is_formatted_clang, 'All code is formatted (clang)'}
	'install_dependencies_for_examples_and_tools_clang': Task{install_dependencies_for_examples_and_tools_clang, 'Install deps for examples/tools (clang)'}
	'recompile_v_with_cstrict_clang':                    Task{recompile_v_with_cstrict_clang, 'Recompile V with -cstrict and clang'}
	'valgrind_clang':                                    Task{valgrind_clang, 'Valgrind (clang)'}
	'run_sanitizers_clang':                              Task{run_sanitizers_clang, 'Run sanitizers (clang)'}
	'v_self_compilation_clang':                          Task{v_self_compilation_clang, 'V self compilation (clang)'}
	'v_self_compilation_usecache_clang':                 Task{v_self_compilation_usecache_clang, 'V self compilation with default caching (clang)'}
	'verify_v_test_works_clang':                         Task{verify_v_test_works_clang, 'Verify `v test` works (clang)'}
	'test_pure_v_math_module_clang':                     Task{test_pure_v_math_module_clang, 'Test pure V math module (clang)'}
	'self_tests_clang':                                  Task{self_tests_clang, 'Self tests (clang)'}
	'self_tests_vprod_clang':                            Task{self_tests_vprod_clang, 'Self tests (vprod) (clang)'}
	'self_tests_cstrict_clang':                          Task{self_tests_cstrict_clang, 'Self tests (-cstrict) (clang)'}
	'build_examples_clang':                              Task{build_examples_clang, 'Build examples (clang)'}
	'build_examples_autofree_clang':                     Task{build_examples_autofree_clang, 'Build examples with -autofree (clang)'}
	'build_modules_clang':                               Task{build_modules_clang, 'Build modules (clang)'}
	'test_inline_assembly':                              Task{test_inline_assembly, 'Test inline assembly'}
}

if os.args.len > 1 && os.args[1] == 'ci' {
	if os.args.len > 3 || (os.args.len == 3 && os.args[2] != '--reset') {
		eprintln('Usage: v run ci/linux_ci.vsh ci [--reset]')
		exit(1)
	}
	run_ci_tasks(os.args.len == 3) or {
		eprintln('Could not update CI progress: ${err.msg()}')
		exit(1)
	}
	exit(0)
}

common.run(all_tasks)