Ctrl K
ComunidadGuías…

ci/macos_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
import runtime

fn test_symlink() {
	exec_args(['v', 'symlink'])
}

fn test_cross_compilation() {
	exec_args(['v', '-o', 'hw', '-os', 'linux', 'examples/hello_world.v'])
	exec_args(['ls', '-la', 'hw'])
	exec_args(['file', 'hw'])
	exec_args(['v', '-d', 'use_openssl', '-o', 've', '-os', 'linux', 'examples/veb/veb_example.v'])
	exec_args(['ls', '-la', 've'])
	exec_args(['file', 've'])
}

fn build_with_cstrict() {
	exec_args(['v', '-cg', '-cstrict', '-o', 'vstrict1', 'cmd/v'])
}

fn all_code_is_formatted() {
	if common.is_github_job {
		exec_args(['env', 'VJOBS=1', 'v', '-silent', 'test-cleancode'])
	} else {
		vjobs := os.getenv_opt('VJOBS') or { '1' }
		exec_args(['env', 'VJOBS=' + '${vjobs}', 'v', '-progress', 'test-cleancode'])
	}
}

fn run_sanitizers() {
	common.exec_args_with_progress(['v', '-o', 'v2', 'cmd/v', '-cflags', '-fsanitize=undefined'], ['v2'])
	exec_args(['env', 'UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1', './v2', '-o', 'v.c',
		'cmd/v'])
}

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

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 install_iconv() {
	// Skip Homebrew when iconv is already linkable for V on this machine.
	if os.system_args([os.join_path(@VEXEROOT, 'v'), '-silent', 'test', 'vlib/encoding/iconv/']) == 0 {
		return
	}
	exec_args(['sh', '-c', 'brew list --versions libiconv >/dev/null 2>&1 || brew install libiconv'])
}

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

fn self_tests() {
	$if macos {
		// Metal frame callbacks stop when the display sleeps. Keep the real GPU
		// probes running, and release the assertion even if a failed command exits.
		mut display_guard := os.new_process('/usr/bin/caffeinate')
		display_guard.set_args(['-d', '-u', '-w', os.getpid().str()])
		display_guard.run()
		defer {
			display_guard.signal_term()
			display_guard.wait()
			display_guard.close()
		}
		run_self_tests()
	} $else {
		run_self_tests()
	}
}

fn run_self_tests() {
	// Do not 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.
	// Individual files still fall back to it when the default compiler cannot
	// build them.
	// Exercise default caching throughout the standard library suite. Respect the
	// workflow's worker count as well as the aggregate runner's serial setting.
	if common.is_github_job {
		vjobs := os.getenv_opt('VJOBS') or {
			if os.getenv('VTEST_FAIL_FAST') == '1' { '1' } else { '${runtime.nr_cpus()}' }
		}
		exec_args(['env', 'VJOBS=' + '${vjobs}', 'v', '-no-memory-limit', '-silent', 'test-self',
			'vlib'])
	} else {
		vjobs := os.getenv_opt('VJOBS') or { '1' }
		exec_args(['env', 'VJOBS=' + '${vjobs}', 'v', '-no-memory-limit', '-progress', 'test-self',
			'vlib'])
	}
}

fn build_examples() {
	if common.is_github_job {
		exec_args(['v', '-no-memory-limit', 'build-examples'])
	} else {
		exec_args(['v', '-no-memory-limit', '-progress', 'build-examples'])
	}
}

fn build_examples_v_compiled_with_tcc() {
	exec_args(['v', '-o', 'vtcc', '-cc', 'tcc', 'cmd/v'])
	if common.is_github_job {
		exec_args(['./vtcc', '-no-memory-limit', 'build-examples'])
	} else {
		exec_args(['./vtcc', '-no-memory-limit', '-progress', 'build-examples'])
	}
}

// ownership_vexe builds, once per job, a V3 compiler with the ownership checker
// compiled in. `-autofree` needs it: a standard V3 build rejects the flag.
fn ownership_vexe() string {
	vexe := './vownership'
	if !os.exists(vexe) {
		exec_args(['v', '-d', 'ownership', '-o', 'vownership', 'cmd/v'])
	}
	return vexe
}

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

fn build_hello_world_autofree() {
	if skip_ownership_autofree_test() {
		eprintln('> skipping ownership/autofree test')
		return
	}
	exec_args([ownership_vexe(), '-autofree', '-o', 'hello_world', 'examples/hello_world.v'])
	exec_args(['./hello_world'])
}

fn build_tetris_autofree() {
	if skip_ownership_autofree_test() {
		eprintln('> skipping ownership/autofree test')
		return
	}
	exec_args([ownership_vexe(), '-autofree', '-o', 'tetris', 'examples/tetris/tetris.v'])
}

fn build_blog_autofree() {
	if skip_ownership_autofree_test() {
		eprintln('> skipping ownership/autofree test')
		return
	}
	// `-autofree` still needs the V1 compatibility compiler, and the frozen V 0.5.2
	// release behind it ships a vlib without `json2`, which the blog imports. Build
	// the tutorial with the default compiler until V3 ownership can run it;
	// build_tetris_autofree keeps the autofree path covered.
	exec_args(['v', '-o', 'blog', 'tutorials/building_a_simple_web_blog_with_veb/code/blog'])
}

fn build_examples_prod() {
	exec_args(['v', '-prod', 'examples/news_fetcher.v'])
}

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

fn build_v_with_prealloc() {
	exec_args(['v', '-cg', '-cstrict', '-o', 'vstrict1', 'cmd/v'])
	exec_args(['./vstrict1', '-d', 'debug_malloc', '-d', 'debug_realloc', '-o', 'vdebug1', 'cmd/v'])
	exec_args(['./vstrict1', '-o', 'vprealloc', '-prealloc', 'cmd/v'])
	// TODO: fix prealloc on macos (the rwmutex implementation for shared maps there seems to require that mutexes are allocated by C.malloc directly, and segfaults for arbitrary memory addresses)
	//	exec_args(['./vprealloc', 'run', 'examples/hello_world.v'])
	//	exec_args(['./vprealloc', '-o', 'v3', 'cmd/v'])
	//	exec_args(['./v3', '-o', 'v4', 'cmd/v'])
}

fn v_self_compilation_usecache() {
	$if !enable_usecache_test ? {
		eprintln('> ${@LOCATION} use `-d enable_usecache_test` in VFLAGS to enable this task')
		return
	}
	exec_args(['v', 'examples/hello_world.v'])
	exec_args(['./examples/hello_world'])
	exec_args(['v', '-o', 'v2', 'cmd/v'])
	exec_args(['./v2', '-o', 'v3', 'cmd/v'])
	exec_args(['./v3', 'version'])
	exec_args(['./v3', '-o', 'tetris', 'examples/tetris/tetris.v'])
}

fn v_self_compilation_parallel_cc() {
	exec_args(['v', '-o', 'vp', '-parallel-cc', 'cmd/v'])
	// exec_args(['./v2', '-o', 'v3', 'cmd/v'])
	exec_args(['./vp', 'version'])
	exec_args(['./vp', '-o', 'tetris', 'examples/tetris/tetris.v'])
}

fn test_password_input() {
	// Expect gives the child a pseudo-terminal, but non-interactive parent shells can
	// still export TERM=dumb, which makes os.input_password reject that usable PTY.
	if os.getenv('TERM') in ['', 'dumb'] {
		os.setenv('TERM', 'xterm', true)
	}
	exec_args(['v', '-silent', 'test', 'examples/password/'])
}

fn test_readline() {
	exec_args(['v', '-silent', 'test', 'examples/readline/'])
}

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

const ci_tasks = [
	'test_symlink',
	'v_doctor',
	'build_v_with_prealloc',
	'test_cross_compilation',
	'test_inline_assembly',
	'build_with_cstrict',
	'all_code_is_formatted',
	'run_sanitizers',
	'build_using_v',
	'verify_v_test_works',
	'install_iconv',
	'test_pure_v_math_module',
	'self_tests',
	'build_examples',
	'build_hello_world_autofree',
	'build_tetris_autofree',
	'build_blog_autofree',
	'build_examples_prod',
	'build_examples_v_compiled_with_tcc',
	'v_self_compilation_parallel_cc',
	'test_password_input',
	'test_readline',
]

// 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 '/tmp/v-macos-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 'macos-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)!
}

// check_macho_signing runs the native signing tests without resume or test filters.
fn check_macho_signing() {
	$if macos {
		vexe := os.getenv_opt('V_CI_VEXE') or { os.join_path_single(@VEXEROOT, 'v') }
		tcc := os.join_path(os.dir(os.real_path(vexe)), 'thirdparty', 'tcc', 'tcc.exe')
		if !os.is_executable(tcc) {
			eprintln('Mach-O signing check requires TinyCC at ${tcc}; run `make` first.')
			exit(1)
		}
		eprintln('Checking Mach-O signing before resuming CI tasks.')
		// Run the file directly so test filters and saved successes cannot skip it.
		exec_args(['env', 'VTEST_ONLY_FN=', 'v', '-silent', 'vlib/v/util/machosign/macho_sign_test.v'])
	}
}

// run_ci_tasks mirrors the active ci/macos_ci.vsh steps in
// .github/workflows/macos_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 job environment that changes test behavior.
	os.setenv('CI', 'true', true)
	os.setenv('GITHUB_ACTIONS', 'true', true)
	os.setenv('GITHUB_JOB', 'clang-macos', true)
	os.setenv('RUNNER_OS', 'macOS', true)
	os.setenv('VFLAGS', '-cc clang', true)
	// Stop within test/build sessions too, without other files already running.
	os.setenv('VTEST_FAIL_FAST', '1', true)
	os.setenv('VJOBS', '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)
	os.setenv('V_MACOS_V3_NO_FALLBACK', '1', true)
	os.setenv('V_MACOS_MULTIWINDOW_TESTS', '0', true)

	check_macho_signing()

	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/macos_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_MACOS_CI_TASK_PROGRESS', os.join_path(progress_dir, task_name), true)
		eprintln('CI task ${i + 1}/${ci_tasks.len}: ${task_name}')
		exec_args(['v', 'run', 'ci/macos_ci.vsh', '${task_name}'])
	}
	os.rmdir_all(progress_dir)!
	os.rm(progress_path)!
	eprintln('CI tasks complete; progress cleared.')
}

const all_tasks = {
	'test_symlink':                       Task{test_symlink, 'Test symlink'}
	'test_cross_compilation':             Task{test_cross_compilation, 'Test cross compilation to Linux'}
	'build_with_cstrict':                 Task{build_with_cstrict, 'Build V with -cstrict'}
	'all_code_is_formatted':              Task{all_code_is_formatted, 'All code is formatted'}
	'run_sanitizers':                     Task{run_sanitizers, 'Run sanitizers'}
	'build_using_v':                      Task{build_using_v, 'Build V using V'}
	'verify_v_test_works':                Task{verify_v_test_works, 'Verify `v test` works'}
	'install_iconv':                      Task{install_iconv, 'Install iconv for encoding.iconv'}
	'test_pure_v_math_module':            Task{test_pure_v_math_module, 'Test pure V math module'}
	'self_tests':                         Task{self_tests, 'Self tests'}
	'build_examples':                     Task{build_examples, 'Build examples'}
	'build_hello_world_autofree':         Task{build_hello_world_autofree, 'Build hello_world with -autofree'}
	'build_tetris_autofree':              Task{build_tetris_autofree, 'Build tetris with -autofree'}
	'build_blog_autofree':                Task{build_blog_autofree, 'Build blog tutorial with -autofree'}
	'build_examples_prod':                Task{build_examples_prod, 'Build examples with -prod'}
	'build_examples_v_compiled_with_tcc': Task{build_examples_v_compiled_with_tcc, 'Build examples with V build with tcc'}
	'v_doctor':                           Task{v_doctor, 'v doctor'}
	'build_v_with_prealloc':              Task{build_v_with_prealloc, 'Build V with prealloc'}
	'v_self_compilation_usecache':        Task{v_self_compilation_usecache, 'V self compilation with default caching'}
	'v_self_compilation_parallel_cc':     Task{v_self_compilation_parallel_cc, 'V self compilation with -parallel-cc'}
	'test_password_input':                Task{test_password_input, 'Test password input'}
	'test_readline':                      Task{test_readline, 'Test readline'}
	'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/macos_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)