11import common { Task, exec }
2+ import crypto.sha256
23import os
34
45// Shared tasks/helpers
@@ -255,21 +256,41 @@ fn self_tests_cstrict_gcc() {
255256 exec('VTEST_JUST_ESSENTIAL=1 V_CI_CSTRICT=1 v -cc gcc -cstrict -silent test-self vlib')
256257}
257258
259+ fn skip_ownership_autofree_test() bool {
260+ return common.is_github_job || os.getenv('VTEST_SKIP_OWNERSHIP') == '1'
261+ }
262+
263+ fn report_skipped_ownership_autofree_test() {
264+ eprintln('> skipping ownership/autofree test')
265+ }
266+
258267fn build_examples_gcc() {
259268 build_examples()
260269}
261270
262271fn build_tetris_autofree_gcc() {
272+ if skip_ownership_autofree_test() {
273+ report_skipped_ownership_autofree_test()
274+ return
275+ }
263276 exec('v -autofree -o tetris examples/tetris/tetris.v')
264277 exec('rm -f tetris')
265278}
266279
267280fn build_blog_autofree_gcc() {
281+ if skip_ownership_autofree_test() {
282+ report_skipped_ownership_autofree_test()
283+ return
284+ }
268285 exec('v -autofree -o blog tutorials/building_a_simple_web_blog_with_veb/code/blog')
269286 exec('rm -f blog')
270287}
271288
272289fn build_option_test_autofree_gcc() {
290+ if skip_ownership_autofree_test() {
291+ report_skipped_ownership_autofree_test()
292+ return
293+ }
273294 exec('v -autofree vlib/v/tests/options/option_test.c.v')
274295}
275296
@@ -382,6 +403,10 @@ fn build_examples_clang() {
382403}
383404
384405fn build_examples_autofree_clang() {
406+ if skip_ownership_autofree_test() {
407+ report_skipped_ownership_autofree_test()
408+ return
409+ }
385410 exec('v -N -W -autofree -experimental -o tetris examples/tetris/tetris.v')
386411 exec('rm -f tetris')
387412}
@@ -404,6 +429,178 @@ fn test_inline_assembly() {
404429 exec('v test vlib/v/slow_tests/assembly')
405430}
406431
432+ // Keep this list in the same order as the active tasks in
433+ // .github/workflows/linux_ci.yml. The aggregate runner changes compiler flags at
434+ // the same boundaries as the three workflow jobs.
435+ const ci_tasks = [
436+ 'build_v_with_prealloc',
437+ 'all_code_is_formatted_tcc',
438+ 'install_dependencies_for_examples_and_tools_tcc',
439+ 'test_v_to_c_tcc',
440+ 'v_self_compilation_tcc',
441+ 'v_doctor_tcc',
442+ 'verify_v_test_works_tcc',
443+ 'test_pure_v_math_module_tcc',
444+ 'test_inline_assembly',
445+ 'self_tests_tcc',
446+ 'build_examples_tcc',
447+ 'run_submodule_example_tcc',
448+ 'build_tools_tcc',
449+ 'build_vbinaries_tcc',
450+ 'build_benches_tcc',
451+ 'run_vsh_script_tcc',
452+ 'test_v_tutorials_tcc',
453+ 'build_fast_tcc',
454+ 'v_self_compilation_usecache_tcc',
455+ 'test_password_input_tcc',
456+ 'test_readline_tcc',
457+ 'test_leak_detector_tcc',
458+ 'test_leak_detector_not_active_tcc',
459+ 'all_code_is_formatted_gcc',
460+ 'install_dependencies_for_examples_and_tools_gcc',
461+ 'recompile_v_with_cstrict_gcc',
462+ 'valgrind_v_c_gcc',
463+ 'run_sanitizers_gcc',
464+ 'v_self_compilation_gcc',
465+ 'v_self_compilation_usecache_gcc',
466+ 'verify_v_test_works_gcc',
467+ 'test_pure_v_math_module_gcc',
468+ 'self_tests_gcc',
469+ 'self_tests_prod_gcc',
470+ 'self_tests_cstrict_gcc',
471+ 'build_examples_gcc',
472+ 'build_tetris_autofree_gcc',
473+ 'build_blog_autofree_gcc',
474+ 'build_option_test_autofree_gcc',
475+ 'v_self_compilation_parallel_cc_gcc',
476+ 'build_modules_gcc',
477+ 'compile_vdoctor_prod_gcc',
478+ 'compile_vup_prod_gcc',
479+ 'all_code_is_formatted_clang',
480+ 'install_dependencies_for_examples_and_tools_clang',
481+ 'recompile_v_with_cstrict_clang',
482+ 'valgrind_clang',
483+ 'run_sanitizers_clang',
484+ 'v_self_compilation_clang',
485+ 'v_self_compilation_usecache_clang',
486+ 'verify_v_test_works_clang',
487+ 'test_pure_v_math_module_clang',
488+ 'self_tests_clang',
489+ 'self_tests_vprod_clang',
490+ 'self_tests_cstrict_clang',
491+ 'build_examples_clang',
492+ 'build_examples_autofree_clang',
493+ 'build_modules_clang',
494+ ]
495+
496+ // Keep progress across edits/rebuilds, but isolate users and checkout directories.
497+ fn ci_progress_path() string {
498+ checkout := sha256.hexhash(os.real_path(os.getwd()))
499+ return os.join_path(os.cache_dir(), 'v-linux-ci-${os.getuid()}-${checkout}.progress')
500+ }
501+
502+ fn ci_progress_contents(task_name string) string {
503+ // Record the whole ordered task list so changed plans restart safely.
504+ return 'linux-ci-v1\n${task_name}\n${ci_tasks.join('\n')}\n'
505+ }
506+
507+ fn ci_resume_index(path string) !int {
508+ if !os.exists(path) {
509+ return -1
510+ }
511+ if !os.is_file(path) {
512+ return error('CI progress path is not a file: ${path}')
513+ }
514+ saved := os.read_file(path)!
515+ for i, task_name in ci_tasks {
516+ if saved == ci_progress_contents(task_name) {
517+ return i
518+ }
519+ }
520+ eprintln('Ignoring invalid or outdated CI progress; restarting from the first task.')
521+ return -1
522+ }
523+
524+ fn save_ci_progress(path string, task_name string) ! {
525+ // Write privately, then rename on the same filesystem. An interrupted write
526+ // leaves the previous checkpoint intact, never a partially written cursor.
527+ if os.exists(path) && !os.is_file(path) {
528+ return error('CI progress path is not a file: ${path}')
529+ }
530+ tmp_dir := '${path}.${os.getpid()}.tmp'
531+ os.mkdir(tmp_dir, mode: 0o700)!
532+ defer {
533+ os.rmdir_all(tmp_dir) or {}
534+ }
535+ tmp_path := os.join_path(tmp_dir, 'progress')
536+ os.write_file(tmp_path, ci_progress_contents(task_name))!
537+ os.rename(tmp_path, path)!
538+ }
539+
540+ fn ci_vflags(task_index int) string {
541+ if task_index < ci_tasks.index('all_code_is_formatted_gcc') {
542+ return '-cc tcc -no-retry-compilation'
543+ }
544+ if task_index < ci_tasks.index('all_code_is_formatted_clang') {
545+ return ''
546+ }
547+ return '-cc clang'
548+ }
549+
550+ // run_ci_tasks mirrors the active ci/linux_ci.vsh steps in
551+ // .github/workflows/linux_ci.yml. The generic `all` mode intentionally remains
552+ // exhaustive, including tasks that are currently disabled in the workflow.
553+ fn run_ci_tasks(reset bool) ! {
554+ // Match the GitHub Actions environment that changes test behavior.
555+ os.setenv('CI', 'true', true)
556+ os.setenv('GITHUB_ACTIONS', 'true', true)
557+ os.setenv('RUNNER_OS', 'Linux', true)
558+ os.setenv('V_MACOS_V3_NO_FALLBACK', '1', true)
559+ // Stop test/build sessions promptly while retaining Linux CI's normal worker count.
560+ os.setenv('VTEST_FAIL_FAST', '1', true)
561+ os.setenv('VTEST_SHOW_LONGEST_BY_RUNTIME', '3', true)
562+ os.setenv('VTEST_SHOW_LONGEST_BY_COMPTIME', '3', true)
563+ os.setenv('VTEST_SHOW_LONGEST_BY_TOTALTIME', '3', true)
564+ os.setenv('VTEST_SKIP_OWNERSHIP', '1', true)
565+
566+ progress_path := ci_progress_path()
567+ progress_dir := '${progress_path}.d'
568+ saved_index := if reset { -1 } else { ci_resume_index(progress_path)! }
569+ // No valid cursor means none of its finer-grained records may be reused.
570+ if saved_index < 0 && os.exists(progress_dir) {
571+ os.rmdir_all(progress_dir)!
572+ }
573+ if !os.exists(progress_dir) {
574+ os.mkdir(progress_dir, mode: 0o700)!
575+ }
576+ start := if saved_index < 0 { 0 } else { saved_index }
577+ os.unsetenv('VTEST_RESUME_OWNER')
578+ eprintln('CI progress: ${progress_path}')
579+ eprintln('Use `v run ci/linux_ci.vsh ci --reset` to restart from the first task.')
580+ if start > 0 {
581+ eprintln('Resuming at ${ci_tasks[start]}; skipping ${start} completed CI tasks.')
582+ }
583+ for i in start .. ci_tasks.len {
584+ task_name := ci_tasks[i]
585+ // Save BEFORE execution: a failure or interruption must retry this task.
586+ save_ci_progress(progress_path, task_name)!
587+ os.setenv('V_CI_TASK_PROGRESS', os.join_path(progress_dir, task_name), true)
588+ os.setenv('VFLAGS', ci_vflags(i), true)
589+ if task_name.ends_with('_tcc') || task_name in ['build_v_with_prealloc', 'test_inline_assembly'] {
590+ os.setenv('GITHUB_JOB', 'tcc-linux', true)
591+ } else if task_name.ends_with('_gcc') {
592+ os.setenv('GITHUB_JOB', 'gcc-linux', true)
593+ } else {
594+ os.setenv('GITHUB_JOB', 'clang-linux', true)
595+ }
596+ eprintln('CI task ${i + 1}/${ci_tasks.len}: ${task_name}')
597+ exec('v run ci/linux_ci.vsh ${task_name}')
598+ }
599+ os.rmdir_all(progress_dir)!
600+ os.rm(progress_path)!
601+ eprintln('CI tasks complete; progress cleared.')
602+ }
603+
407604// Collect all tasks
408605const all_tasks = {
409606 'build_v_with_prealloc': Task{build_v_with_prealloc, 'Build V with prealloc'}
@@ -469,4 +666,16 @@ const all_tasks = {
469666 'test_inline_assembly': Task{test_inline_assembly, 'Test inline assembly'}
470667}
471668
669+ if os.args.len > 1 && os.args[1] == 'ci' {
670+ if os.args.len > 3 || (os.args.len == 3 && os.args[2] != '--reset') {
671+ eprintln('Usage: v run ci/linux_ci.vsh ci [--reset]')
672+ exit(1)
673+ }
674+ run_ci_tasks(os.args.len == 3) or {
675+ eprintln('Could not update CI progress: ${err.msg()}')
676+ exit(1)
677+ }
678+ exit(0)
679+ }
680+
472681common.run(all_tasks)
0 commit comments