Merge remote-tracking branch 'origin/master' into perf/loom-transform-2
This commit is contained in:
commit
cf850c2eae
61 files changed
+1622
-168
No files matched your search
+3
-1
@@ -69,7 +69,9 @@ its checkpoints, so the following invocation starts from the beginning.
|
||||
|
||||
The aggregate `ci` command sets `VTEST_FAIL_FAST=1` and `VJOBS=1`, overriding inherited
|
||||
values. Test and example-build sessions stop at the first reported failure, with no
|
||||
other files already running in parallel. `test-cleancode` also stops after a failed
|
||||
other files already running in parallel. The `self_tests` task retains this single-worker
|
||||
setting; standalone GitHub Actions self-test tasks still use every available CPU.
|
||||
`test-cleancode` also stops after a failed
|
||||
vetting session instead of starting formatting checks. The failed task's checkpoint
|
||||
is retained. Existing per-test retry and known-flaky-test policies are unchanged.
|
||||
|
||||
|
||||
+4
-3
@@ -65,10 +65,11 @@ fn self_tests() {
|
||||
// Individual files still fall back to it when the default compiler cannot
|
||||
// build them.
|
||||
// Exercise default caching throughout the standard library suite. Use every
|
||||
// core on the 7 GB runners instead of the one-job memory default.
|
||||
// core on the 7 GB runners unless the aggregate runner requires fail-fast.
|
||||
if common.is_github_job {
|
||||
exec_args(['env', 'VJOBS=' + '${runtime.nr_cpus()}', 'v', '-no-memory-limit', '-silent',
|
||||
'test-self', 'vlib'])
|
||||
vjobs := 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',
|
||||
|
||||
@@ -37,6 +37,12 @@ if [ "$1" = doctor ]; then
|
||||
echo doctor >> direct.log
|
||||
exit 0
|
||||
fi
|
||||
if [ "$1" = -no-memory-limit ]; then
|
||||
[ "$2" = -silent ] && [ "$3" = test-self ] && [ "$4" = vlib ] || exit 98
|
||||
[ "${VTEST_FAIL_FAST:-}" = 1 ] && [ "${VJOBS:-}" = 1 ] || exit 99
|
||||
echo self-tests >> self.log
|
||||
exit 0
|
||||
fi
|
||||
[ "$1" = run ] && [ "$2" = ci/macos_ci.vsh ] || exit 90
|
||||
[ "$CI" = true ] && [ "$GITHUB_ACTIONS" = true ] || exit 91
|
||||
[ "$VFLAGS" = '-cc clang' ] && [ "$V_MACOS_V3_NO_FALLBACK" = 1 ] || exit 92
|
||||
@@ -128,6 +134,13 @@ run_ci 0 v_doctor
|
||||
cmp "$work/saved" "$checkpoint"
|
||||
[ "$(cat "$checkout/direct.log")" = doctor ] || fail 'individual task did not run'
|
||||
|
||||
# Execute the real self_tests task: its GitHub branch must retain the aggregate
|
||||
# runner's serial fail-fast setting instead of replacing it with the CPU count.
|
||||
(cd "$checkout" && GITHUB_JOB=clang-macos VTEST_FAIL_FAST=1 VJOBS=1 \
|
||||
V_CI_VEXE="$fake_v" "$runner" self_tests) > "$work/output" 2>&1 || \
|
||||
fail 'self_tests replaced the fail-fast worker count'
|
||||
[ "$(cat "$checkout/self.log")" = self-tests ] || fail 'self_tests did not run'
|
||||
|
||||
# A second checkout must not consume the first checkout's saved progress.
|
||||
first_checkout=$checkout
|
||||
checkout="$work/other checkout"
|
||||
|
||||
@@ -154,3 +154,9 @@ Registry-qualified dependency keys remain distinct in lock data.
|
||||
The candidate search behind `resolve_with_pubgrub` is test-only: it delegates to the consistent
|
||||
backtracking search, which the test suite exercises. Dependency constraints are checked in both
|
||||
directions; a complete conflict-driven PubGrub algorithm is pending.
|
||||
|
||||
On Windows, VPM enables Git `core.longpaths` for the Git processes it starts. Existing
|
||||
`GIT_CONFIG_PARAMETERS` and indexed `GIT_CONFIG_*` entries are preserved, with the long-path
|
||||
setting appended using the assignment syntax supported before Git 2.31. Invalid or unextendable
|
||||
indexed counts are left unchanged and reported.
|
||||
Local Windows `file:///C:/...` origins compare consistently with ordinary drive paths.
|
||||
@@ -28,7 +28,7 @@ struct ErrorOptions {
|
||||
verbose bool // is used to only output the error message if the verbose setting is enabled.
|
||||
}
|
||||
|
||||
const vexe = os.quoted_path(os.getenv('VEXE'))
|
||||
const vexe = os.getenv('VEXE')
|
||||
const home_dir = os.home_dir()
|
||||
const selected_server_url_env = 'VPM_SELECTED_SERVER_URL'
|
||||
const vpm_http_timeout = 10 * time.second
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
// vtest build: network?
|
||||
import os
|
||||
import time
|
||||
import rand
|
||||
import v.vmod
|
||||
import test_utils { cmd_ok_args }
|
||||
|
||||
const v = os.quoted_path(@VEXE)
|
||||
const v = @VEXE
|
||||
const test_path = os.join_path(os.vtmp_dir(), 'vpm_dependency_test_${rand.ulid()}')
|
||||
|
||||
fn testsuite_begin() {
|
||||
$if !network ? {
|
||||
eprintln('> skipping ${@FILE}, when `-d network` is missing')
|
||||
exit(0)
|
||||
}
|
||||
dump(test_path)
|
||||
test_utils.set_test_env(test_path)
|
||||
os.mkdir_all(test_path) or {}
|
||||
@@ -69,7 +66,6 @@ fn test_install_dependencies_in_module_dir() {
|
||||
fn test_resolve_external_dependencies_during_module_install() {
|
||||
res := cmd_ok_args(@LOCATION, ['${v}', 'install', '-v',
|
||||
'https://github.com/ttytm/emoji-mart-desktop'])
|
||||
assert res.output.contains('Found 2 dependencies'), res.output
|
||||
expect_installing(@LOCATION, res.output, 'ttytm.webview')
|
||||
expect_installing(@LOCATION, res.output, 'miniaudio')
|
||||
// `ttytm.webview` is a registered VPM module, so it lands in `<vmodules_dir>/ttytm/webview`.
|
||||
|
||||
@@ -8,7 +8,7 @@ import test_utils { cmd_fail_args, cmd_ok_args }
|
||||
const test_path = os.join_path(os.vtmp_dir(), 'vpm_install_local_test_${rand.ulid()}')
|
||||
|
||||
struct LocalInstallCase {
|
||||
args string
|
||||
args []string
|
||||
module_name string
|
||||
workdir string
|
||||
}
|
||||
@@ -21,52 +21,59 @@ fn testsuite_end() {
|
||||
os.rmdir_all(test_path) or {}
|
||||
}
|
||||
|
||||
fn test_install_from_local_git_repository_variants() {
|
||||
fn test_install_from_local_git_repository_variants() ! {
|
||||
repo_path := os.join_path(test_path, 'local_repo')
|
||||
repo_dot_git_path := os.join_path(test_path, 'local_repo.git')
|
||||
create_local_git_module(repo_path, 'local_repo_pkg')
|
||||
create_local_git_module(repo_dot_git_path, 'local_repo_dot_git_pkg')
|
||||
// A temporary root with a space in it, such as a profile path, must still be one argument.
|
||||
spaced_repo_path := os.join_path(test_path, 'space dir', 'local repo')
|
||||
create_local_git_module(spaced_repo_path, 'spaced_repo_pkg')
|
||||
|
||||
cases := [
|
||||
LocalInstallCase{
|
||||
args: os.quoted_path(repo_path)
|
||||
args: [spaced_repo_path]
|
||||
module_name: 'spaced_repo_pkg'
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: [repo_path]
|
||||
module_name: 'local_repo_pkg'
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: file_url(repo_path)
|
||||
args: [file_url(repo_path)]
|
||||
module_name: 'local_repo_pkg'
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: '--git ${os.quoted_path(repo_path)}'
|
||||
args: ['--git', repo_path]
|
||||
module_name: 'local_repo_pkg'
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: '--git ${file_url(repo_path)}'
|
||||
args: ['--git', file_url(repo_path)]
|
||||
module_name: 'local_repo_pkg'
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: os.file_name(repo_path)
|
||||
args: [os.file_name(repo_path)]
|
||||
module_name: 'local_repo_pkg'
|
||||
workdir: test_path
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: os.quoted_path(repo_dot_git_path)
|
||||
args: [repo_dot_git_path]
|
||||
module_name: 'local_repo_dot_git_pkg'
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: file_url(repo_dot_git_path)
|
||||
args: [file_url(repo_dot_git_path)]
|
||||
module_name: 'local_repo_dot_git_pkg'
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: '--git ${os.quoted_path(repo_dot_git_path)}'
|
||||
args: ['--git', repo_dot_git_path]
|
||||
module_name: 'local_repo_dot_git_pkg'
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: '--git ${file_url(repo_dot_git_path)}'
|
||||
args: ['--git', file_url(repo_dot_git_path)]
|
||||
module_name: 'local_repo_dot_git_pkg'
|
||||
},
|
||||
LocalInstallCase{
|
||||
args: os.file_name(repo_dot_git_path)
|
||||
args: [os.file_name(repo_dot_git_path)]
|
||||
module_name: 'local_repo_dot_git_pkg'
|
||||
workdir: test_path
|
||||
},
|
||||
@@ -74,12 +81,13 @@ fn test_install_from_local_git_repository_variants() {
|
||||
for i, c in cases {
|
||||
vmodules_path := os.join_path(test_path, 'vmodules_case_${i}')
|
||||
test_utils.set_test_env(vmodules_path)
|
||||
cmd := '${vexe} install ${c.args}'
|
||||
old_dir := os.getwd()
|
||||
if c.workdir != '' {
|
||||
os.chdir(c.workdir) or { panic(err) }
|
||||
}
|
||||
res := cmd_ok_args(@LOCATION, [vexe, 'install', '${c.args}'])
|
||||
mut argv := [vexe, 'install']
|
||||
argv << c.args
|
||||
res := cmd_ok_args(@LOCATION, argv)
|
||||
if c.workdir != '' {
|
||||
os.chdir(old_dir) or {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// vtest build: !musl? && !sanitized_job?
|
||||
// vtest build: network? && !musl? && !sanitized_job?
|
||||
// vtest retry: 3
|
||||
module main
|
||||
|
||||
@@ -12,10 +12,6 @@ import test_utils { cmd_fail_args, cmd_ok_args }
|
||||
const test_path = os.join_path(os.vtmp_dir(), 'vpm_install_test_${rand.ulid()}')
|
||||
|
||||
fn testsuite_begin() {
|
||||
$if !network ? {
|
||||
eprintln('> skipping ${@FILE}, when `-d network` is missing')
|
||||
exit(0)
|
||||
}
|
||||
dump(test_path)
|
||||
test_utils.set_test_env(test_path)
|
||||
}
|
||||
@@ -57,7 +53,7 @@ fn test_install_from_git_url() {
|
||||
assert res.output.contains('`http` is deprecated'), res.output
|
||||
manifest = get_vmod(os.join_path('wertzui123', 'hashmap'))
|
||||
res = cmd_ok_args(@LOCATION, [os.getenv('VEXE'), 'install', 'http://github.com/Wertzui123/HashMap'])
|
||||
assert res.output.contains('Updating module `wertzui123.hashmap`'), res.output
|
||||
assert res.output.contains('Module `HashMap` is already installed.'), res.output
|
||||
assert res.output.contains('`http` is deprecated'), res.output
|
||||
res = cmd_ok_args(@LOCATION, [os.getenv('VEXE'), 'install', 'https://gitlab.com/tobealive/webview'])
|
||||
assert res.output.contains('Installed `webview`'), res.output
|
||||
@@ -76,20 +72,20 @@ fn test_install_from_git_url_uses_registered_package_name() ! {
|
||||
assert manifest.name == 'nedpals.args'
|
||||
|
||||
res = cmd_ok_args(@LOCATION, [os.getenv('VEXE'), 'install', 'https://github.com/nedpals/v-args'])
|
||||
assert res.output.contains('Updating module `nedpals.args`'), res.output
|
||||
assert res.output.contains('Module `nedpals.args` is already installed.'), res.output
|
||||
manifest = get_vmod(os.join_path('nedpals', 'args'))
|
||||
assert manifest.name == 'nedpals.args'
|
||||
}
|
||||
|
||||
fn test_install_already_existent() {
|
||||
mut res := cmd_ok_args(@LOCATION, [os.getenv('VEXE'), 'install', 'https://github.com/vlang/markdown'])
|
||||
assert res.output.contains('Updating module `markdown`'), res.output
|
||||
assert res.output.contains('Module `markdown` is already installed.'), res.output
|
||||
manifest := get_vmod('markdown')
|
||||
assert manifest.name == 'markdown'
|
||||
assert manifest.dependencies == []string{}
|
||||
// The same module but with the `.git` extension added.
|
||||
res = cmd_ok_args(@LOCATION, [os.getenv('VEXE'), 'install', 'https://github.com/vlang/markdown.git'])
|
||||
assert res.output.contains('Updating module `markdown`'), res.output
|
||||
assert res.output.contains('Module `markdown` is already installed.'), res.output
|
||||
}
|
||||
|
||||
fn test_install_once() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// vtest build: !musl? && !sanitized_job?
|
||||
// vtest build: network? && !musl? && !sanitized_job?
|
||||
// vtest retry: 3
|
||||
import os
|
||||
import rand
|
||||
@@ -14,10 +14,6 @@ const expect_exe = os.quoted_path(os.find_abs_path_of_executable('expect') or {
|
||||
})
|
||||
|
||||
fn testsuite_begin() {
|
||||
$if !network ? {
|
||||
eprintln('> skipping ${@FILE}, when `-d network` is missing')
|
||||
exit(0)
|
||||
}
|
||||
dump(test_path)
|
||||
test_utils.set_test_env(test_path)
|
||||
// Explicitly disable fail on prompt.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// vtest build: !musl? && !sanitized_job?
|
||||
// vtest build: network? && !musl? && !sanitized_job?
|
||||
// vtest retry: 3
|
||||
module main
|
||||
|
||||
@@ -10,10 +10,6 @@ import test_utils { cmd_fail_args, cmd_ok_args }
|
||||
const test_path = os.join_path(os.vtmp_dir(), 'vpm_install_version_test_${rand.ulid()}')
|
||||
|
||||
fn testsuite_begin() {
|
||||
$if !network ? {
|
||||
eprintln('> skipping ${@FILE}, when `-d network` is missing')
|
||||
exit(0)
|
||||
}
|
||||
dump(test_path)
|
||||
test_utils.set_test_env(test_path)
|
||||
}
|
||||
@@ -41,7 +37,7 @@ fn test_install_from_vpm_with_git_version_tag() {
|
||||
assert manifest.version == '0.6.0'
|
||||
// Install same version without force flag.
|
||||
res = cmd_ok_args(@LOCATION, [vexe, 'install', '${ident}' + '@' + '${tag}'])
|
||||
assert res.output.contains('Module `${ident}@${tag}` is already installed, use --force to overwrite'), res.output
|
||||
assert res.output.contains('Module `${ident}@${tag}` is already installed.'), res.output
|
||||
// Install another version, add force flag to surpass confirmation.
|
||||
tag = 'v0.5.0'
|
||||
res = cmd_ok_args(@LOCATION, [vexe, 'install', '-f', '${ident}' + '@' + '${tag}'])
|
||||
@@ -52,18 +48,18 @@ fn test_install_from_vpm_with_git_version_tag() {
|
||||
// Install invalid version.
|
||||
tag = '6.0'
|
||||
res = cmd_fail_args(@LOCATION, [vexe, 'install', '-f', '${ident}' + '@' + '${tag}'])
|
||||
assert res.output.contains('failed to install `${ident}`'), res.output
|
||||
assert res.output.contains('failed to install `${ident}@${tag}`'), res.output
|
||||
|
||||
// Install invalid version verbose.
|
||||
res = cmd_fail_args(@LOCATION, [vexe, 'install', '-f', '-v', '${ident}' + '@' + '${tag}'])
|
||||
assert res.output.contains('failed to install `${ident}`'), res.output
|
||||
assert res.output.contains('failed to install `${ident}@${tag}`'), res.output
|
||||
assert res.output.contains('Remote branch 6.0 not found in upstream origin'), res.output
|
||||
// Install without version tag after a version was installed
|
||||
res = cmd_ok_args(@LOCATION, [vexe, 'install', '-f', '${ident}'])
|
||||
assert res.output.contains('Installing `${ident}`'), res.output
|
||||
// Re-install latest version (without a tag). Should trigger an update, force should not be required.
|
||||
// Re-install latest version (without a tag). The same revision is already installed, so it is skipped without force.
|
||||
res = cmd_ok_args(@LOCATION, [vexe, 'install', '${ident}'])
|
||||
assert res.output.contains('Updating module `${ident}`'), res.output
|
||||
assert res.output.contains('Module `${ident}` is already installed.'), res.output
|
||||
}
|
||||
|
||||
fn test_install_from_git_url_with_version_tag() {
|
||||
@@ -77,7 +73,7 @@ fn test_install_from_git_url_with_version_tag() {
|
||||
assert manifest.version == '0.1.50'
|
||||
// Install same version without force flag.
|
||||
res = cmd_ok_args(@LOCATION, [vexe, 'install', '${url}' + '@' + '${tag}'])
|
||||
assert res.output.contains('Module `vsl@${tag}` is already installed, use --force to overwrite'), res.output
|
||||
assert res.output.contains('Module `vsl@${tag}` is already installed.'), res.output
|
||||
// Install another version, add force flag to surpass confirmation.
|
||||
tag = 'v0.1.47'
|
||||
res = cmd_ok_args(@LOCATION, [vexe, 'install', '-f', '${url}' + '@' + '${tag}'])
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
module main
|
||||
|
||||
// The stored origin of a module installed on Windows is a `file:///S:/...` URL.
|
||||
// It must normalise to the same value as the plain path, or pins are never matched.
|
||||
fn test_normalized_clone_source_matches_windows_drive_file_urls() {
|
||||
$if windows {
|
||||
path := @DIR
|
||||
url := 'file:///' + path.replace('\\', '/')
|
||||
normalized := normalized_clone_source(url)
|
||||
assert normalized == normalized_clone_source(path)
|
||||
assert normalized.all_after('file://').count(':') == 1, normalized
|
||||
}
|
||||
}
|
||||
|
||||
// On POSIX, `/a:/...` is an ordinary path, not a drive path. Stripping its slash
|
||||
// would make it relative, so the same URL would normalise differently from each
|
||||
// working directory.
|
||||
fn test_normalized_clone_source_keeps_posix_paths_with_a_colon_after_one_character() {
|
||||
$if !windows {
|
||||
assert normalized_clone_source('file:///a:/repo') == 'file:///a:/repo'
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// vtest retry: 3
|
||||
// vtest build: !windows
|
||||
// vtest build: network? && !windows
|
||||
module main
|
||||
|
||||
import os
|
||||
@@ -9,10 +9,6 @@ import test_utils { cmd_ok_args }
|
||||
const test_path = os.join_path(os.vtmp_dir(), 'vpm_outdated_test_${rand.ulid()}')
|
||||
|
||||
fn testsuite_begin() {
|
||||
$if !network ? {
|
||||
eprintln('> skipping ${@FILE}, when `-d network` is missing')
|
||||
exit(0)
|
||||
}
|
||||
dump(test_path)
|
||||
test_utils.set_test_env(test_path)
|
||||
os.mkdir_all(test_path)!
|
||||
|
||||
@@ -59,6 +59,11 @@ fn override_project(directory string, deps []string, overrides []string) !string
|
||||
return project
|
||||
}
|
||||
|
||||
// lockfile_repo_path returns a repository path in the form the lockfile stores it: forward slashes.
|
||||
fn lockfile_repo_path(path string) string {
|
||||
return path.replace('\\', '/')
|
||||
}
|
||||
|
||||
fn test_override_selects_source_manifest_dependencies_and_locked_revision() {
|
||||
leaf := override_repo('leaf-source')!
|
||||
leaf_one := override_tag(leaf, 'leaf', 'v1.0.0', '0.0.1', [], [])!
|
||||
@@ -75,8 +80,8 @@ fn test_override_selects_source_manifest_dependencies_and_locked_revision() {
|
||||
assert override_git(os.join_path(store, 'pkg'), ['rev-parse', 'HEAD']) == parent_two
|
||||
assert override_git(os.join_path(store, 'leaf'), ['rev-parse', 'HEAD']) == leaf_one
|
||||
assert os.read_file(os.join_path(store, 'leaf', 'marker.txt'))! == 'v1.0.0'
|
||||
entry := read_lockfile(project)!.modules[lockfile_module_key(dep)]!
|
||||
assert entry.requested == parent + '@v2.0.0'
|
||||
entry := read_lockfile(project)!.modules[lockfile_module_key(lockfile_repo_path(dep))]!
|
||||
assert entry.requested == lockfile_repo_path(parent) + '@v2.0.0'
|
||||
assert entry.resolved == 'v2.0.0'
|
||||
assert entry.revision == parent_two
|
||||
override_tag(parent, 'pkg', 'v3.0.0', '0.0.1', [leaf + '@v2.0.0'], [])!
|
||||
@@ -98,7 +103,7 @@ fn test_override_selects_source_manifest_dependencies_and_locked_revision() {
|
||||
cmd_ok_args(@LOCATION, [override_vpm_exe, 'install', '-f'])
|
||||
assert os.read_file(os.join_path(fresh, 'pkg', 'marker.txt'))! == 'v3.0.0'
|
||||
assert os.read_file(os.join_path(fresh, 'leaf', 'marker.txt'))! == 'v2.0.0'
|
||||
assert read_lockfile(project)!.modules[lockfile_module_key(dep)]!.requested == parent + '@v3.0.0'
|
||||
assert read_lockfile(project)!.modules[lockfile_module_key(lockfile_repo_path(dep))]!.requested == lockfile_repo_path(parent) + '@v3.0.0'
|
||||
}
|
||||
|
||||
fn test_override_range_resolves_and_locks_actual_tag() {
|
||||
@@ -108,8 +113,8 @@ fn test_override_range_resolves_and_locks_actual_tag() {
|
||||
project := override_project('range-project', [repo + '@missing-tag'], ['range_pkg: ^2.0.0'])!
|
||||
test_utils.set_test_env(os.join_path(override_test_root, 'range-store'))
|
||||
cmd_ok_args(@LOCATION, [override_vpm_exe, 'install'])
|
||||
entry := read_lockfile(project)!.modules[repo]!
|
||||
assert entry.requested == repo + '@^2.0.0'
|
||||
entry := read_lockfile(project)!.modules[lockfile_repo_path(repo)]!
|
||||
assert entry.requested == lockfile_repo_path(repo) + '@^2.0.0'
|
||||
assert entry.resolved == 'v2.1.0'
|
||||
assert entry.revision == selected
|
||||
override_tag(repo, 'range_pkg', 'v2.2.0', '0.0.1', [], [])!
|
||||
@@ -132,7 +137,7 @@ fn test_selector_override_selects_the_requiring_edges_source_before_min_v() {
|
||||
cmd_ok_args(@LOCATION, [override_vpm_exe, 'install'])
|
||||
assert override_git(os.join_path(store, 'c'), ['rev-parse', 'HEAD']) == selected
|
||||
assert os.read_file(os.join_path(store, 'c', 'marker.txt'))! == 'v1.0.0'
|
||||
assert read_lockfile(project)!.modules[leaf]!.requested == leaf + '@v1.0.0'
|
||||
assert read_lockfile(project)!.modules[lockfile_repo_path(leaf)]!.requested == lockfile_repo_path(leaf) + '@v1.0.0'
|
||||
other := override_repo('selector-other')!
|
||||
override_tag(other, 'other', 'v1.0.0', '0.0.1', [leaf + '@v2.0.0'], [])!
|
||||
override_project('selector-other-project', [other + '@v1.0.0'], ['requiring>c: v1.0.0'])!
|
||||
|
||||
+10
-1
@@ -378,7 +378,16 @@ fn (m Module) existing_checkout_matches_source() bool {
|
||||
|
||||
fn normalized_clone_source(raw_source string) string {
|
||||
raw := raw_source.trim_space()
|
||||
local_path := os.expand_tilde_to_home(raw.trim_string_left('file://'))
|
||||
mut local_path := os.expand_tilde_to_home(raw.trim_string_left('file://'))
|
||||
// `file:///S:/x` keeps a slash before the drive letter. Drop it, or os.real_path
|
||||
// resolves `/S:/x` against the current drive and gives `S:\S:\x`. Only Windows
|
||||
// has drive letters: on POSIX `/a:/x` is an ordinary absolute path, so keep it.
|
||||
$if windows {
|
||||
if local_path.len > 2 && local_path[0] == `/` && local_path[1].is_letter()
|
||||
&& local_path[2] == `:` {
|
||||
local_path = local_path[1..]
|
||||
}
|
||||
}
|
||||
if raw.starts_with('file://') || os.is_abs_path(local_path) || raw.starts_with('./')
|
||||
|| raw.starts_with('../') || raw.starts_with('~/') || os.exists(local_path) {
|
||||
return 'file://' + os.real_path(local_path)
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// vtest build: network?
|
||||
// vtest retry: 3
|
||||
import os
|
||||
import rand
|
||||
import test_utils
|
||||
|
||||
const vexe = os.quoted_path(@VEXE)
|
||||
const vexe = @VEXE
|
||||
const test_path = os.join_path(os.vtmp_dir(), 'vpm_remove_test_${rand.ulid()}')
|
||||
|
||||
fn testsuite_begin() {
|
||||
$if !network ? {
|
||||
eprintln('> skipping ${@FILE}, when `-d network` is missing')
|
||||
exit(0)
|
||||
}
|
||||
dump(test_path)
|
||||
test_utils.set_test_env(test_path)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
// vtest build: network?
|
||||
// vtest retry: 3
|
||||
module main
|
||||
|
||||
import test_utils { cmd_ok_args }
|
||||
|
||||
fn testsuite_begin() {
|
||||
$if !network ? {
|
||||
eprintln('> skipping ${@FILE}, when `-d network` is missing')
|
||||
exit(0)
|
||||
}
|
||||
unbuffer_stdout()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
// vtest build: !musl? && !sanitized_job?
|
||||
// vtest build: network? && !musl? && !sanitized_job?
|
||||
// vtest retry: 3
|
||||
import os
|
||||
import rand
|
||||
import test_utils { cmd_fail_args, cmd_ok_args }
|
||||
|
||||
const v = os.quoted_path(@VEXE)
|
||||
const v = @VEXE
|
||||
const test_path = os.join_path(os.vtmp_dir(), 'vpm_update_test_${rand.ulid()}')
|
||||
|
||||
fn testsuite_begin() {
|
||||
$if !network ? {
|
||||
eprintln('> skipping ${@FILE}, when `-d network` is missing')
|
||||
exit(0)
|
||||
}
|
||||
dump(test_path)
|
||||
test_utils.set_test_env(test_path)
|
||||
}
|
||||
|
||||
+55
-5
@@ -26,12 +26,12 @@ const vcs_info = init_vcs_info() or {
|
||||
exit(1)
|
||||
}
|
||||
|
||||
fn init_vcs_info() !map[VCS]VCSInfo {
|
||||
git_installed_raw_ver := parse_git_version(os.exec_opt(['git', '--version'])!.output) or { '' }
|
||||
git_installed_ver := semver.from(git_installed_raw_ver)!
|
||||
// git_clone_install_args returns the options `git clone` takes before the url and
|
||||
// path, for the installed git `git_ver` on `os_name`.
|
||||
fn git_clone_install_args(git_ver semver.Version, os_name string) ![]string {
|
||||
git_shallow_submod_ver := semver.from('2.36.0')!
|
||||
mut git_install_args := ['clone', '--recursive']
|
||||
if os.user_os() != 'windows' {
|
||||
if os_name != 'windows' {
|
||||
// The variation of environment factors on windows is too high;
|
||||
// the following options are known to work well on != windows,
|
||||
// but can sometimes cause failures on windows for yet unknown reasons,
|
||||
@@ -39,7 +39,7 @@ fn init_vcs_info() !map[VCS]VCSInfo {
|
||||
// for more details, about why this is now allowed only on != windows platforms.
|
||||
// Keep module blobs in the initial pack. Partial clones need another network
|
||||
// fetch during checkout, which can stall installations such as `v install ui2`.
|
||||
if git_installed_ver >= git_shallow_submod_ver {
|
||||
if git_ver >= git_shallow_submod_ver {
|
||||
git_install_args << '--shallow-submodules'
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,13 @@ fn init_vcs_info() !map[VCS]VCSInfo {
|
||||
// 13.6 MB with it, for an identical working tree.
|
||||
git_install_args << '--depth'
|
||||
git_install_args << '1'
|
||||
return git_install_args
|
||||
}
|
||||
|
||||
fn init_vcs_info() !map[VCS]VCSInfo {
|
||||
git_installed_raw_ver := parse_git_version(os.exec_opt(['git', '--version'])!.output) or { '' }
|
||||
git_installed_ver := semver.from(git_installed_raw_ver)!
|
||||
git_install_args := git_clone_install_args(git_installed_ver, os.user_os())!
|
||||
return {
|
||||
VCS.git: VCSInfo{
|
||||
dir: '.git'
|
||||
@@ -74,6 +81,49 @@ fn init_vcs_info() !map[VCS]VCSInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// git_config_count returns how many GIT_CONFIG_* entries the environment already
|
||||
// holds, or none when GIT_CONFIG_COUNT is set to anything but a plain decimal count.
|
||||
fn git_config_count() ?int {
|
||||
text := os.getenv('GIT_CONFIG_COUNT')
|
||||
if text == '' {
|
||||
return 0
|
||||
}
|
||||
count := text.int()
|
||||
if count < 0 || count == 2147483647 || count.str() != text {
|
||||
return none
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// enable_git_longpaths sets core.longpaths for every git process vpm starts after
|
||||
// main begins. The `git --version` call that initializes vcs_info runs before that, and
|
||||
// reads no config, so it does not need the setting.
|
||||
// Windows git keeps the 260-character path limit unless that setting is on, and
|
||||
// vpm's module cache is deep enough to exceed it. The setting goes in two variables:
|
||||
// GIT_CONFIG_COUNT, which git 2.31 and later read, and GIT_CONFIG_PARAMETERS, the
|
||||
// variable `git -c` fills in, which every git version reads, so it also covers git
|
||||
// before 2.31. Existing entries in both are kept, and the new setting is appended
|
||||
// after them. A GIT_CONFIG_COUNT that is not a plain non-negative decimal is left
|
||||
// alone and reported, since extending it would overwrite or hide the user's entries.
|
||||
// The new entries come last, so a core.longpaths=false the user already set is
|
||||
// overridden for the git processes vpm starts.
|
||||
fn enable_git_longpaths() {
|
||||
mut params := os.getenv('GIT_CONFIG_PARAMETERS')
|
||||
if params != '' {
|
||||
params += ' '
|
||||
}
|
||||
os.setenv('GIT_CONFIG_PARAMETERS', "${params}'core.longpaths=true'", true)
|
||||
count := git_config_count() or {
|
||||
vpm_warn('not adding core.longpaths to GIT_CONFIG_COUNT.',
|
||||
details: 'GIT_CONFIG_COUNT is not a plain non-negative integer, so vpm leaves it alone. Git may exit with an error on it.'
|
||||
)
|
||||
return
|
||||
}
|
||||
os.setenv('GIT_CONFIG_KEY_${count}', 'core.longpaths', true)
|
||||
os.setenv('GIT_CONFIG_VALUE_${count}', 'true', true)
|
||||
os.setenv('GIT_CONFIG_COUNT', '${count + 1}', true)
|
||||
}
|
||||
|
||||
fn (vcs VCS) clone_args(url string, version string, path string) ![]string {
|
||||
if version.contains('\0') || version.contains('\r') || version.contains('\n') {
|
||||
return error('version contains NUL, CR, or LF')
|
||||
|
||||
@@ -50,6 +50,102 @@ fn test_clone_args() {
|
||||
assert !head_args.contains('--single-branch')
|
||||
}
|
||||
|
||||
// save_git_config_env and restore_git_config_env keep these tests from leaking
|
||||
// GIT_CONFIG_* variables, or the HOME and XDG_CONFIG_HOME config locations, into the
|
||||
// rest of the test process.
|
||||
fn save_git_config_env() map[string]string {
|
||||
mut saved := map[string]string{}
|
||||
for name in ['GIT_CONFIG_COUNT', 'GIT_CONFIG_KEY_0', 'GIT_CONFIG_VALUE_0', 'GIT_CONFIG_KEY_2',
|
||||
'GIT_CONFIG_VALUE_2', 'GIT_CONFIG_PARAMETERS', 'GIT_CONFIG_GLOBAL', 'GIT_CONFIG_NOSYSTEM',
|
||||
'HOME', 'XDG_CONFIG_HOME'] {
|
||||
saved[name] = os.getenv(name)
|
||||
}
|
||||
return saved
|
||||
}
|
||||
|
||||
fn restore_git_config_env(saved map[string]string) {
|
||||
for name, value in saved {
|
||||
if value == '' {
|
||||
os.unsetenv(name)
|
||||
} else {
|
||||
os.setenv(name, value, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A count that cannot be extended must be left alone: rewriting it would overwrite
|
||||
// the user's own config entries, or hide them from git. GIT_CONFIG_PARAMETERS is
|
||||
// separate, so the setting still reaches git through it.
|
||||
fn test_enable_git_longpaths_leaves_an_invalid_count_alone() {
|
||||
saved := save_git_config_env()
|
||||
defer {
|
||||
restore_git_config_env(saved)
|
||||
}
|
||||
for invalid in ['-1', 'two', '2 ', '+2', '99999999999999999999', '2147483647'] {
|
||||
os.unsetenv('GIT_CONFIG_KEY_0')
|
||||
os.unsetenv('GIT_CONFIG_PARAMETERS')
|
||||
os.setenv('GIT_CONFIG_COUNT', invalid, true)
|
||||
enable_git_longpaths()
|
||||
assert os.getenv('GIT_CONFIG_COUNT') == invalid, invalid
|
||||
assert os.getenv('GIT_CONFIG_KEY_0') == '', invalid
|
||||
assert os.getenv('GIT_CONFIG_PARAMETERS') == "'core.longpaths=true'", invalid
|
||||
}
|
||||
}
|
||||
|
||||
fn test_enable_git_longpaths_appends_after_existing_entries() {
|
||||
saved := save_git_config_env()
|
||||
defer {
|
||||
restore_git_config_env(saved)
|
||||
}
|
||||
os.unsetenv('GIT_CONFIG_KEY_2')
|
||||
os.setenv('GIT_CONFIG_COUNT', '2', true)
|
||||
os.setenv('GIT_CONFIG_PARAMETERS', "'core.autocrlf=false'", true)
|
||||
enable_git_longpaths()
|
||||
assert os.getenv('GIT_CONFIG_COUNT') == '3'
|
||||
assert os.getenv('GIT_CONFIG_KEY_2') == 'core.longpaths'
|
||||
assert os.getenv('GIT_CONFIG_VALUE_2') == 'true'
|
||||
assert os.getenv('GIT_CONFIG_PARAMETERS') == "'core.autocrlf=false' 'core.longpaths=true'"
|
||||
os.unsetenv('GIT_CONFIG_COUNT')
|
||||
os.unsetenv('GIT_CONFIG_KEY_0')
|
||||
os.unsetenv('GIT_CONFIG_PARAMETERS')
|
||||
enable_git_longpaths()
|
||||
assert os.getenv('GIT_CONFIG_COUNT') == '1'
|
||||
assert os.getenv('GIT_CONFIG_KEY_0') == 'core.longpaths'
|
||||
assert os.getenv('GIT_CONFIG_PARAMETERS') == "'core.longpaths=true'"
|
||||
}
|
||||
|
||||
// Git before 2.31 ignores GIT_CONFIG_COUNT, but every git version reads
|
||||
// GIT_CONFIG_PARAMETERS. The COUNT entries are dropped after enabling, so only
|
||||
// GIT_CONFIG_PARAMETERS can carry core.longpaths to git.
|
||||
fn test_enable_git_longpaths_reaches_git_without_config_count() {
|
||||
saved := save_git_config_env()
|
||||
defer {
|
||||
restore_git_config_env(saved)
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'vpm_longpaths_${rand.ulid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer {
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
os.setenv('GIT_CONFIG_PARAMETERS', "'core.autocrlf=false'", true)
|
||||
enable_git_longpaths()
|
||||
os.unsetenv('GIT_CONFIG_COUNT')
|
||||
os.unsetenv('GIT_CONFIG_KEY_0')
|
||||
os.unsetenv('GIT_CONFIG_VALUE_0')
|
||||
// Isolate git from the machine's own config, which may already set core.longpaths.
|
||||
// GIT_CONFIG_GLOBAL is only read by git 2.32 and later, so HOME and XDG_CONFIG_HOME
|
||||
// also move the user config into the test root for the older git this test is for.
|
||||
empty_config := os.join_path(root, 'empty.gitconfig')
|
||||
os.write_file(empty_config, '')!
|
||||
os.setenv('HOME', root, true)
|
||||
os.setenv('XDG_CONFIG_HOME', root, true)
|
||||
os.setenv('GIT_CONFIG_NOSYSTEM', '1', true)
|
||||
os.setenv('GIT_CONFIG_GLOBAL', empty_config, true)
|
||||
res := os.exec(['git', '-C', root, 'config', '--get', 'core.longpaths'])
|
||||
assert res.exit_code == 0, res.output
|
||||
assert res.output.trim_space() == 'true'
|
||||
}
|
||||
|
||||
fn test_git_clone_contains_historical_blobs() {
|
||||
root := os.join_path(os.vtmp_dir(), 'vpm_clone_blobs_${rand.ulid()}')
|
||||
repo := os.join_path(root, 'source')
|
||||
|
||||
@@ -21,6 +21,9 @@ const excluded_dirs = ['.cache', 'vlib']
|
||||
fn main() {
|
||||
unbuffer_stdout()
|
||||
os.setenv(selected_server_url_env, '', true)
|
||||
$if windows {
|
||||
enable_git_longpaths()
|
||||
}
|
||||
// This tool is intended to be launched by the v frontend,
|
||||
// which provides the path to V inside os.getenv('VEXE')
|
||||
// args are: vpm [options] SUBCOMMAND module names
|
||||
|
||||
+4
-2
@@ -2,6 +2,7 @@ module main
|
||||
|
||||
import os
|
||||
import v.cmdexec
|
||||
import v.pref
|
||||
import v.skills
|
||||
import v.util.version
|
||||
import v.util.recompilation
|
||||
@@ -218,7 +219,8 @@ fn tcc_cache_requests(root string, requests []cache_refresh.Request) []cache_ref
|
||||
fn is_tcc_cache_compiler(path string, bundled string, cwd string) bool {
|
||||
resolved := os.real_path(path)
|
||||
name := os.file_name(resolved).to_lower_ascii()
|
||||
if resolved == os.real_path(bundled) || name.contains('tcc') || name.contains('tinyc') {
|
||||
// Same file-name rule as the launcher and driver, so `notcc` or `distcc` is not TinyCC by name.
|
||||
if resolved == os.real_path(bundled) || pref.tinycc_file_name(name) {
|
||||
return true
|
||||
}
|
||||
// Match the driver's effective compiler classification, including renamed
|
||||
@@ -230,7 +232,7 @@ fn is_tcc_cache_compiler(path string, bundled string, cwd string) bool {
|
||||
if os.user_os() in ['macos', 'ios'] && resolved == '/usr/bin/cc' { return false }
|
||||
if !os.is_executable(path) { return false }
|
||||
compiler_version := cmdexec.run_in(path, ['--version'], cwd).output.to_lower_ascii()
|
||||
return compiler_version.contains('tiny c compiler') || compiler_version.contains('tcc version')
|
||||
return pref.ccompiler_version_is_tinycc(compiler_version)
|
||||
}
|
||||
|
||||
// skills_refresh_hint is what the report tells the user to run.
|
||||
|
||||
+21
-16
@@ -56,9 +56,11 @@ fn rebundle(fake_vroot string, name string) ! {
|
||||
// It returns the checkout, the home directory, the built tool, and a PATH
|
||||
// holding only the stand-ins.
|
||||
fn vup_report_fixture() !(string, string, string, string) {
|
||||
root := os.join_path(os.vtmp_dir(), 'vup_report_${os.getpid()}')
|
||||
mut root := os.join_path(os.vtmp_dir(), 'vup_report_${os.getpid()}')
|
||||
os.rmdir_all(root) or {}
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
// Match the checkout's physical path when macOS exposes /tmp through /private/tmp.
|
||||
root = os.real_path(root)
|
||||
home := os.join_path(root, 'home')
|
||||
os.mkdir_all(os.join_path(home, '.agents', 'skills')) or { panic(err) }
|
||||
for name in ['alpha', 'beta', 'gamma'] {
|
||||
@@ -154,20 +156,19 @@ fn test_vup_refreshes_only_tinycc_modes_even_when_already_updated() ! {
|
||||
os.write_file(cache_log, '')!
|
||||
result := run_vup(root, home, tool, bin_dir, ...options)!
|
||||
assert result.exit_code == 0, result.output
|
||||
assert result.output.count('Caching module crypto.sample...') == 4, result.output
|
||||
assert result.output.count('> Refreshing module cache [') == 4, result.output
|
||||
assert result.output.contains('> Refreshing module cache [1/4]'), result.output
|
||||
assert result.output.contains('> Refreshing module cache [4/4]'), result.output
|
||||
assert result.output.contains('> Module cache refresh complete: 4 build configurations checked.'), result.output
|
||||
assert result.output.count('Caching module crypto.sample...') == 2, result.output
|
||||
assert result.output.count('> Refreshing module cache [') == 2, result.output
|
||||
assert result.output.contains('> Refreshing module cache [1/2]'), result.output
|
||||
assert result.output.contains('> Refreshing module cache [2/2]'), result.output
|
||||
assert result.output.contains('> Module cache refresh complete: 2 build configurations checked.'), result.output
|
||||
assert result.output.contains('(normal program; -cc tcc).\nCaching module crypto.sample...'), result.output
|
||||
assert result.output.contains('(production program; -cc tcc -prod).\nCaching module crypto.sample...'), result.output
|
||||
assert result.output.contains('(normal tests; -cc tcc).\nCaching module crypto.sample...'), result.output
|
||||
assert result.output.contains('(production tests; -cc tcc -prod).\nCaching module crypto.sample...'), result.output
|
||||
// The production TinyCC records stay on disk from before the rule, and are not refreshed.
|
||||
calls := os.read_file(cache_log)!.split_into_lines()
|
||||
assert calls.len == 4, calls.str()
|
||||
assert calls.len == 2, calls.str()
|
||||
assert calls.all(it.starts_with('-cc tcc ')), calls.str()
|
||||
assert calls.any(it.starts_with('-cc tcc -skip-running ')), calls.str()
|
||||
assert calls.any(it.starts_with('-cc tcc -prod -skip-running ')), calls.str()
|
||||
assert !calls.any(it.contains('-prod')), calls.str()
|
||||
assert !calls.any(it.contains(' run ')), calls.str()
|
||||
}
|
||||
// A failed refresh is visible and fails v up, while other modules still refresh.
|
||||
@@ -180,7 +181,7 @@ fn test_vup_refreshes_only_tinycc_modes_even_when_already_updated() ! {
|
||||
assert failure.exit_code != 0, failure.output
|
||||
assert failure.output.contains('refreshing cached vlib modules failed'), failure.output
|
||||
assert !failure.output.contains('> Module cache refresh complete:'), failure.output
|
||||
assert os.read_file(cache_log)!.split_into_lines().len == 4
|
||||
assert os.read_file(cache_log)!.split_into_lines().len == 2
|
||||
}
|
||||
|
||||
fn test_vup_resolves_relative_cache_paths_before_entering_checkout() ! {
|
||||
@@ -268,7 +269,8 @@ fn test_vup_recognizes_tinycc_paths_aliases_and_default_records() ! {
|
||||
write_executable(wrapper, '#!/bin/sh\necho probe >> ' + os.quoted_path(probes) +
|
||||
'\necho "tcc version 0.9.27"\n')!
|
||||
other := os.join_path(bin_dir, 'other-compiler')
|
||||
write_executable(other, '#!/bin/sh\necho "clang version 18.0.0"\n')!
|
||||
write_executable(other, '#!/bin/sh\necho "clang version 19.1.7"\n' +
|
||||
'echo "InstalledDir: /opt/tinycc-tcc version-compatible-sdk/bin"\n')!
|
||||
relative := os.path_rel(os.getwd(), bundled)!
|
||||
variants := [['-cc', 'tinyc'], ['-cc', bundled], ['-cc', alias], ['-cc', relative], []string{},
|
||||
['-cc', os.join_path(bin_dir, 'cc')], ['-cc'], ['-cc', renamed], ['-cc', copied],
|
||||
@@ -305,7 +307,8 @@ fn test_vup_recognizes_tinycc_paths_aliases_and_default_records() ! {
|
||||
result := run_vup(root, home, tool, bin_dir)!
|
||||
assert result.exit_code == 0, result.output
|
||||
calls := os.read_file(log)!.split_into_lines()
|
||||
assert calls.len == 9, calls.str()
|
||||
// The production wrapper resolves to TinyCC, so it is skipped with the other tcc+prod records.
|
||||
assert calls.len == 8, calls.str()
|
||||
assert calls.any(it.starts_with('-cc tinyc ')), calls.str()
|
||||
assert calls.any(it.starts_with('-cc ${bundled} ')), calls.str()
|
||||
assert calls.any(it.starts_with('-cc ${alias} ')), calls.str()
|
||||
@@ -313,16 +316,18 @@ fn test_vup_recognizes_tinycc_paths_aliases_and_default_records() ! {
|
||||
assert calls.any(it.starts_with('-cc ${renamed} ')), calls.str()
|
||||
assert calls.any(it.starts_with('-cc ${copied} ')), calls.str()
|
||||
assert calls.any(it.starts_with('-cc ${wrapper} -skip-running ')), calls.str()
|
||||
assert calls.any(it.starts_with('-cc ${wrapper} -prod ')), calls.str()
|
||||
assert !calls.any(it.starts_with('-cc ${wrapper} -prod ')), calls.str()
|
||||
assert calls.filter(it.starts_with('-cc ${context_wrapper} ')).len == 1, calls.str()
|
||||
assert !calls.any(it.contains('-cc ${os.join_path(bin_dir, 'cc')} ')), calls.str()
|
||||
assert !calls.any(it.contains('-cc ${other} ')), calls.str()
|
||||
assert os.read_file(probes)!.split_into_lines() == ['probe']
|
||||
// vup probes the wrapper once for its cache selection, and the refresh filter probes the
|
||||
// production record again before skipping it.
|
||||
assert os.read_file(probes)!.split_into_lines() == ['probe', 'probe']
|
||||
contexts := os.read_file(context_probes)!.split_into_lines()
|
||||
assert contexts.len == 2, contexts.str()
|
||||
assert os.join_path(root, 'tinycc_project') in contexts, contexts.str()
|
||||
assert os.join_path(root, 'clang_project') in contexts, contexts.str()
|
||||
assert result.output.contains('9 build configurations checked.'), result.output
|
||||
assert result.output.contains('8 build configurations checked.'), result.output
|
||||
}
|
||||
|
||||
fn test_vup_rebuilds_changed_tinycc_module_and_reuses_unrelated_module() ! {
|
||||
|
||||
@@ -678,6 +678,58 @@ fn run_launcher_test_process(executable string, args []string, work_dir string,
|
||||
return result
|
||||
}
|
||||
|
||||
fn test_prod_with_tinycc_is_refused_before_any_compiler_runs() {
|
||||
dir := os.join_path(os.vtmp_dir(), 'v_prod_tinycc_${os.getpid()}')
|
||||
os.mkdir_all(dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(dir) or {}
|
||||
}
|
||||
source := os.join_path(dir, 'main.v')
|
||||
os.write_file(source, 'fn main() {\n\tprintln(1)\n}\n') or { panic(err) }
|
||||
output := os.join_path(dir, 'main')
|
||||
// `-old-compiler` must not reach the compatibility compiler with this pair either.
|
||||
for args in [['-prod', '-cc', 'tcc'], ['-cc=tinyc', '-prod'],
|
||||
['-old-compiler', '-prod', '-cc', 'tcc']] {
|
||||
result := os.exec([@VEXE, ...args, '-o', output, source])
|
||||
assert result.exit_code == 1, '${args}: ${result.output}'
|
||||
assert result.output.contains('tcc is not allowed with -prod'), '${args}: ${result.output}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_prod_refuses_tinycc_under_another_name_once_it_resolves() {
|
||||
bundled := os.join_path(os.dir(@VEXE), 'thirdparty', 'tcc', 'tcc.exe')
|
||||
if !os.is_file(bundled) {
|
||||
return
|
||||
}
|
||||
dir := os.join_path(os.vtmp_dir(), 'v_prod_tinycc_alias_${os.getpid()}')
|
||||
os.mkdir_all(dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(dir) or {}
|
||||
}
|
||||
// The name has no `tcc`, so only the resolved compiler gives it away.
|
||||
alias_path := os.join_path(dir, 'cc_alias.exe')
|
||||
os.cp(bundled, alias_path) or { panic(err) }
|
||||
source := os.join_path(dir, 'main.v')
|
||||
os.write_file(source, 'fn main() {\n\tprintln(1)\n}\n') or { panic(err) }
|
||||
// `-old-compiler` must refuse the alias too, before the compatibility compiler is started.
|
||||
for args in [['-prod', '-cc', alias_path], ['-old-compiler', '-prod', '-cc', alias_path]] {
|
||||
result := os.exec([@VEXE, ...args, '-o', os.join_path(dir, 'main'), source])
|
||||
assert result.exit_code == 1, '${args}: ${result.output}'
|
||||
assert result.output.contains('tcc is not allowed with -prod'), '${args}: ${result.output}'
|
||||
assert !result.output.contains('retrying with'), '${args}: ${result.output}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_prod_with_tinycc_in_vflags_does_not_block_version_or_help() {
|
||||
mut environment := os.environ()
|
||||
environment['VFLAGS'] = '-prod -cc tcc'
|
||||
for args in [['version'], ['help']] {
|
||||
result := run_launcher_test_process(@VEXE, args, os.getwd(), environment)
|
||||
assert result.exit_code == 0, '${args}: ${result.output}'
|
||||
assert !result.output.contains('tcc is not allowed with -prod'), '${args}: ${result.output}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_external_tools_do_not_use_the_v1_fallback() {
|
||||
$if !windows {
|
||||
false_executable := os.find_abs_path_of_executable('false') or { return }
|
||||
@@ -930,3 +982,33 @@ fn test_v1_fallback_rejects_copied_current_launcher() {
|
||||
assert result.output.starts_with('V ${v_version} ')
|
||||
assert !v1_fallback_has_expected_version(copy)
|
||||
}
|
||||
|
||||
fn test_private_tool_builds_keep_tools_available_with_tinycc_production_flags() {
|
||||
assert external_tool_compile_args('vfmt', ['-prod', '-cc', 'tcc']) == ['-cc', 'tcc']
|
||||
assert external_tool_args_without_prod(['-prod', '-o', '-prod', '-cc', 'tcc']) == [
|
||||
'-o',
|
||||
'-prod',
|
||||
'-cc',
|
||||
'tcc',
|
||||
]
|
||||
assert external_tool_runtime_args('self', ['-prod', '-cc', 'tcc'], ['self']) == [
|
||||
'-prod',
|
||||
'-cc',
|
||||
'tcc',
|
||||
'self',
|
||||
]
|
||||
}
|
||||
|
||||
fn test_cold_formatter_cache_accepts_tinycc_production_environment() {
|
||||
if !os.is_executable(os.join_path(os.dir(@VEXE), 'thirdparty', 'tcc', 'tcc.exe')) { return }
|
||||
root := os.join_path(os.vtmp_dir(), 'launcher_prod_tcc_tool_${os.getpid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
mut environment := os.environ()
|
||||
environment['VFLAGS'] = '-prod -cc tcc -gc none'
|
||||
environment['VTOOLS_CACHE_DIR'] = os.join_path(root, 'tools')
|
||||
environment['V_MACOS_V3_NO_FALLBACK'] = '1'
|
||||
result := run_launcher_test_process(@VEXE, ['fmt', '--help'], os.getwd(), environment)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert !result.output.contains('tcc is not allowed with -prod'), result.output
|
||||
}
|
||||
@@ -142,6 +142,16 @@ fn main() {
|
||||
eprintln('unknown option `-usecache`; caching is enabled by default, use `-nocache` to disable it')
|
||||
exit(1)
|
||||
}
|
||||
// TinyCC is not a -prod compiler. Refuse the pair before any compiler starts, including the
|
||||
// compatibility compiler that `-old-compiler` selects.
|
||||
if command !in external_commands
|
||||
&& command !in ['version', '-version', '--version', 'help', '-h', '--help', 'new', 'init',
|
||||
'install', 'link', 'list', 'outdated', 'remove', 'registry', 'search', 'show', 'unlink',
|
||||
'update', 'upgrade', 'vlib-docs', 'get', 'interpret']
|
||||
&& pref.tcc_with_prod_requested(compiler_args) {
|
||||
eprintln('tcc is not allowed with -prod')
|
||||
exit(1)
|
||||
}
|
||||
if command in ['version', '-version', '--version'] {
|
||||
println('V ${v_version} ${@VCURRENTHASH}')
|
||||
return
|
||||
@@ -580,9 +590,34 @@ fn external_tool_compile_args(tool_name string, prefix_args []string) []string {
|
||||
}
|
||||
compile_args << ['-gc', 'none']
|
||||
}
|
||||
if pref.tcc_with_prod_requested(compile_args) {
|
||||
compile_args = external_tool_args_without_prod(compile_args)
|
||||
}
|
||||
return compile_args
|
||||
}
|
||||
|
||||
// external_tool_args_without_prod removes only the private helper's production option.
|
||||
// Tool runtime arguments retain the caller's requested compiler configuration.
|
||||
fn external_tool_args_without_prod(args []string) []string {
|
||||
mut result := []string{cap: args.len}
|
||||
mut value_follows := false
|
||||
for i, arg in args {
|
||||
if value_follows {
|
||||
result << arg
|
||||
value_follows = false
|
||||
continue
|
||||
}
|
||||
if arg == '-prod' { continue }
|
||||
result << arg
|
||||
value_follows = if arg in ['-prof', '-profile'] {
|
||||
v1_fallback_profile_option_consumes_value(args, i)
|
||||
} else {
|
||||
arg == '-cf' || pref.option_may_consume_value(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fn external_tool_args_without_target(args []string) []string {
|
||||
mut result := []string{cap: args.len}
|
||||
mut skip_target_value := false
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Wait for the program to enable raw mode before sending input to its terminal.
|
||||
proc wait_for_raw_mode {timeout_seconds} {
|
||||
global spawn_out
|
||||
if {[info exists spawn_out(replica,name)]} {
|
||||
set tty $spawn_out(replica,name)
|
||||
} else {
|
||||
set tty $spawn_out(slave,name)
|
||||
}
|
||||
set deadline [expr {[clock milliseconds] + 1000 * $timeout_seconds}]
|
||||
while {[clock milliseconds] < $deadline} {
|
||||
if {[catch {exec stty -a < $tty} mode]} {
|
||||
return 0
|
||||
}
|
||||
if {[string match "*-icanon*" $mode]} {
|
||||
return 1
|
||||
}
|
||||
after 25
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
# Allow a cold compiler/module-cache startup before the interactive prompt.
|
||||
set timeout 10
|
||||
source [file join [file dirname [info script]] raw_mode.tcl]
|
||||
set v_root [exec sh -c "git rev-parse --show-toplevel"]
|
||||
|
||||
spawn $v_root/v run $v_root/examples/readline/readline.v
|
||||
# Give compilation its own deadline; keystroke responses still use timeout above.
|
||||
if {![wait_for_raw_mode 60]} { exit 1 }
|
||||
|
||||
send "a"
|
||||
expect "got 97" {} timeout { exit 1 }
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
# Allow a cold compiler/module-cache startup before the interactive prompt.
|
||||
set timeout 10
|
||||
source [file join [file dirname [info script]] raw_mode.tcl]
|
||||
set v_root [exec sh -c "git rev-parse --show-toplevel"]
|
||||
# Use input arguments for send and expect.
|
||||
set send_ [lindex $argv 0]
|
||||
set expect_ [lindex $argv 1]
|
||||
|
||||
spawn $v_root/v run $v_root/examples/readline/readline.v
|
||||
# Give compilation its own deadline; keystroke responses still use timeout above.
|
||||
if {![wait_for_raw_mode 60]} { exit 1 }
|
||||
|
||||
send $send_
|
||||
expect $expect_ {} timeout { exit 1 }
|
||||
|
||||
+32
-1
@@ -473,6 +473,33 @@ and builds with bundled TinyCC on macOS and Linux. TinyCC builds and links its o
|
||||
objects. Pass `-nocache` or `--no-cache` to disable caching. A first TinyCC build can be
|
||||
faster without the cache, while later builds reuse the cached modules and executable.
|
||||
The former `-usecache` flag is no longer accepted.
|
||||
The C backend rejects `-prod` with TinyCC, including compiler aliases and renamed copies.
|
||||
Use Clang or GCC for production builds. Help, formatter, and other tool commands remain available
|
||||
with those flags. `v.pref.ccompiler_version_is_tinycc` recognizes TinyCC banner lines;
|
||||
mentions in metadata such as Clang's `InstalledDir` do not identify TinyCC.
|
||||
Compiler probes follow the launch path, including executable files in the working directory
|
||||
before `PATH`. Windows explicit paths use the implicit `.exe` suffix and expand `%NAME%` variables;
|
||||
renamed PE files with other extensions can also be queried.
|
||||
|
||||
Cached programs retain the element layouts required by fixed-array aliases and option payloads.
|
||||
Cached standard-library headers retain their source classification so headerless C bindings keep
|
||||
their complete layouts, including the Boehm GC stack type used by interface stringification.
|
||||
Generated fixed-array map-key helpers declare their cached builtin callees before their definitions.
|
||||
Inferred anonymous fixed arrays use their materialized element types, including nested arrays.
|
||||
Literal-output programs still instantiate retained generic helpers with `-no-skip-unused`.
|
||||
Merged imports in cached headers do not create parameter-name conflicts across source files.
|
||||
Warm builds retain every `IError` implementer's `msg` and `code` methods and their helpers,
|
||||
including calls made by dispatchers inside cached objects.
|
||||
Selected option/result tuple signatures retain nested struct payload layouts, including signatures
|
||||
discovered after cache demand collection, and declare them before their tuples and wrappers.
|
||||
Shared wrappers retain their payload layouts even when only compile-time reflection visits them.
|
||||
Recursive interface string helpers belong to the interface's declaration module, including generic
|
||||
applications whose arguments name types from other modules.
|
||||
Warm builds preserve generic interface parameter metadata after temporary compiler arenas are freed.
|
||||
Translated programs regenerate their C prefixes after body edits so new runtime helpers are present;
|
||||
their module and executable caches remain enabled.
|
||||
Portable cross output keeps its embedded signal-handler installer local so cached builds can
|
||||
also link the native helper object.
|
||||
With `-v` or `-show-timings`, the compiler prints `Caching module <name>...` to standard error
|
||||
before compiling a new module object, including during `run`. Ordinary builds and warm builds
|
||||
print no cache creation notices; `-silent` suppresses them too.
|
||||
@@ -481,7 +508,11 @@ This applies to SHA3 and other imported modules with TinyCC too.
|
||||
It preserves import order for module C flags and reuses the same records on repeated updates.
|
||||
Both original entries and temporary importers stop after module object validation,
|
||||
before compiling program objects or linking an executable.
|
||||
`v up` refreshes only TinyCC cache configurations, retaining recorded production and test modes.
|
||||
`v up` refreshes only TinyCC cache configurations, retaining recorded test modes.
|
||||
Production TinyCC records made by older compilers are skipped.
|
||||
`v.pref.tcc_with_prod_requested_in` checks compiler wrappers in the record's working directory
|
||||
and resolves relative compiler paths and `PATH` entries there without changing the caller's
|
||||
directory or environment.
|
||||
Caches for other C compilers are refreshed by the next build that uses that compiler.
|
||||
Compiler updates validate fresh module interfaces and reuse compiled objects when the generated C,
|
||||
C compiler, flags, and native inputs still match. Reused objects print no cache creation notices.
|
||||
|
||||
@@ -162,29 +162,32 @@ fn test_dump_flags_after_cached_incbin_build_uses_arrays() {
|
||||
assert !flags.contains('_v_embed_blob_'), flags
|
||||
}
|
||||
|
||||
fn test_macos_tcc_build_keeps_the_array_form() {
|
||||
fn assert_tcc_prod_build_rejected(name string) {
|
||||
exe := os.join_path(incbin_workspace, name)
|
||||
main_file := os.join_path(incbin_workspace, 'main.v')
|
||||
build := os.exec([incbin_vexe, '-prod', '-cc', 'tcc', '-no-retry-compilation', '-o', exe,
|
||||
main_file])
|
||||
assert build.exit_code != 0, build.output
|
||||
assert build.output.contains('tcc is not allowed with -prod'), build.output
|
||||
assert !os.exists(exe + $if windows { '.exe' } $else { '' })
|
||||
}
|
||||
|
||||
fn test_macos_tcc_prod_build_is_rejected_before_creating_output() {
|
||||
$if macos {
|
||||
bundled_tcc := os.join_path(@VEXEROOT, 'thirdparty', 'tcc', 'tcc.exe')
|
||||
if !os.is_file(bundled_tcc) {
|
||||
return
|
||||
}
|
||||
build, output := build_and_run('app_tcc', '-cc tcc -no-retry-compilation')
|
||||
assert output == expected_output()
|
||||
assert !build.output.contains('.S -o'), build.output
|
||||
assert_tcc_prod_build_rejected('app_tcc_prod_rejected')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_windows_tcc_build_keeps_the_array_form() {
|
||||
fn test_windows_tcc_prod_build_is_rejected_before_creating_output() {
|
||||
$if windows {
|
||||
bundled_tcc := os.join_path(@VEXEROOT, 'thirdparty', 'tcc', 'tcc.exe')
|
||||
if !os.is_file(bundled_tcc) {
|
||||
return
|
||||
}
|
||||
// `-no-retry-compilation` turns off the retry that would hide the problem: TCC
|
||||
// rejects the object the host assembler produced for the payload, so with the
|
||||
// incbin path this build only succeeds through a fallback to that host.
|
||||
build, output := build_and_run('app_tcc', '-cc tcc -no-retry-compilation')
|
||||
assert output == expected_output()
|
||||
assert !build.output.contains('.S -o'), build.output
|
||||
assert_tcc_prod_build_rejected('app_tcc_prod_rejected')
|
||||
}
|
||||
}
|
||||
@@ -199,11 +199,6 @@ fn test_v_self_accepts_fastc_backend() {
|
||||
assert v2_repeated_build.output.count('V self compiling') == 2, v2_repeated_build.output
|
||||
assert os.is_executable(deep_self_output)
|
||||
|
||||
prod_build := cmdexec.run(@VEXE, ['self', '-silent', '-prod', '-b', 'fastc', '-o',
|
||||
os.join_path(root, 'v prod')])
|
||||
assert prod_build.exit_code != 0
|
||||
assert prod_build.output.contains('does not support `-prod`'), prod_build.output
|
||||
|
||||
source := os.join_path(root, 'main.v')
|
||||
write_fastc_test_source(source, 'module main\nfn main() { println(42) }\n')
|
||||
program := os.join_path(root, 'program')
|
||||
@@ -277,8 +272,9 @@ fn test_fastc_backend_parses_directly_to_c_without_ast_fallback() {
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
v3_bin := os.join_path(root, 'v3')
|
||||
build := cmdexec.run(@VEXE, ['-gc', 'none', '-path', '${fastc_backend_vlib_dir}|@vlib|@vmodules',
|
||||
'-o', v3_bin, fastc_backend_v3_source])
|
||||
// A standalone vlib/v/v.v compiler includes the fastc backend only when it is built with it.
|
||||
build := cmdexec.run(@VEXE, ['-gc', 'none', '-compile-backend', 'fastc', '-path',
|
||||
'${fastc_backend_vlib_dir}|@vlib|@vmodules', '-o', v3_bin, fastc_backend_v3_source])
|
||||
assert build.exit_code == 0, build.output
|
||||
|
||||
collision_source := os.join_path(root, 'collision.v')
|
||||
@@ -311,8 +307,9 @@ fn main() {
|
||||
}
|
||||
")
|
||||
valid_binary := os.join_path(root, 'valid')
|
||||
valid_compile := cmdexec.run(v3_bin, ['-macos-v3-compat-c99', '-b', 'fastc', '-o', valid_binary,
|
||||
valid_source])
|
||||
// Step lines are printed only with -show-timings (see the driver's bench setup).
|
||||
valid_compile := cmdexec.run(v3_bin, ['-macos-v3-compat-c99', '-show-timings', '-b', 'fastc',
|
||||
'-o', valid_binary, valid_source])
|
||||
assert valid_compile.exit_code == 0, valid_compile.output
|
||||
assert valid_compile.output.contains('fastc parse+gen'), valid_compile.output
|
||||
assert !valid_compile.output.contains(' check'), valid_compile.output
|
||||
@@ -778,11 +775,6 @@ fn main() {
|
||||
}
|
||||
|
||||
for invocation in [
|
||||
UnsupportedFastCInvocation{
|
||||
args: ['-silent', '-prod', '-b', 'fastc', '-o', os.join_path(root, 'prod'),
|
||||
valid_source]
|
||||
expected: 'fastc parser does not support `-prod`'
|
||||
},
|
||||
UnsupportedFastCInvocation{
|
||||
args: ['-silent', '-b', 'fastc', '-d', 'no_main', '-o', no_main_output, valid_source]
|
||||
expected: 'fastc parser does not support `-d no_main`'
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import os
|
||||
|
||||
// The invocations below stop before any C is generated, so they run on every host.
|
||||
// `-prod` refuses TinyCC, and FastC refuses `-prod` in both `v` and `v self`.
|
||||
|
||||
fn prod_refusal_root(name string) string {
|
||||
root := os.join_path(os.vtmp_dir(), 'v_prod_refusal_${name}_${os.getpid()}')
|
||||
os.rmdir_all(root) or {}
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
os.write_file(os.join_path(root, 'main.v'), 'module main\nfn main() { println(42) }\n') or {
|
||||
panic(err)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
fn test_prod_refuses_tinycc_before_any_compiler_runs() {
|
||||
root := prod_refusal_root('tinycc')
|
||||
defer {
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
source := os.join_path(root, 'main.v')
|
||||
for args in [['-prod', '-cc', 'tcc'], ['-cc=tinyc', '-prod'],
|
||||
['-old-compiler', '-prod', '-cc', 'tcc']] {
|
||||
result := os.exec([@VEXE, ...args, '-o', os.join_path(root, 'main'), source])
|
||||
assert result.exit_code == 1, '${args}: ${result.output}'
|
||||
assert result.output.contains('tcc is not allowed with -prod'), '${args}: ${result.output}'
|
||||
assert (os.ls(root) or { []string{} }) == ['main.v'], '${args}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_prod_refuses_fastc_before_it_writes_output() {
|
||||
root := prod_refusal_root('fastc')
|
||||
defer {
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
source := os.join_path(root, 'main.v')
|
||||
for output in ['prod', 'prod.c'] {
|
||||
result := os.exec([@VEXE, '-silent', '-prod', '-b', 'fastc', '-o', os.join_path(root, output),
|
||||
source])
|
||||
assert result.exit_code != 0, result.output
|
||||
assert result.output.contains('fastc parser does not support `-prod`'), result.output
|
||||
assert (os.ls(root) or { []string{} }) == ['main.v'], output
|
||||
}
|
||||
}
|
||||
|
||||
fn test_self_refuses_prod_with_fastc() {
|
||||
root := prod_refusal_root('self')
|
||||
defer {
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
result := os.exec([@VEXE, 'self', '-silent', '-prod', '-b', 'fastc', '-o',
|
||||
os.join_path(root, 'v prod')])
|
||||
assert result.exit_code != 0, result.output
|
||||
assert result.output.contains('does not support `-prod`'), result.output
|
||||
assert (os.ls(root) or { []string{} }) == ['main.v'], result.output
|
||||
}
|
||||
|
||||
fn test_prod_tinycc_flags_allow_information_commands() {
|
||||
for command in ['version', 'help'] {
|
||||
result := os.exec([@VEXE, '-prod', '-cc', 'tcc', command])
|
||||
assert result.exit_code == 0, result.output
|
||||
assert !result.output.contains('tcc is not allowed with -prod'), result.output
|
||||
}
|
||||
}
|
||||
|
||||
fn test_old_compiler_prod_refuses_a_tinycc_alias() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
root := prod_refusal_root('alias')
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
alias := os.join_path(root, 'cc')
|
||||
os.write_file(alias, '#!/bin/sh\nprintf "tcc version 0.9.27\\n"\n')!
|
||||
os.chmod(alias, 0o700)!
|
||||
result := os.exec([@VEXE, '-old-compiler', '-prod', '-cc', alias, os.join_path(root, 'main.v')])
|
||||
assert result.exit_code == 1, result.output
|
||||
assert result.output.contains('tcc is not allowed with -prod'), result.output
|
||||
}
|
||||
@@ -135,7 +135,7 @@ fn test_module_cache_refresh_reads_only_project_flags_in_refresh_children() ! {
|
||||
assert v3_cache_refresh_project_flags()! == []cache_refresh.FlagGroup{}
|
||||
}
|
||||
|
||||
fn test_cached_tcc_main_command_preserves_production_optimization() ! {
|
||||
fn test_cached_tcc_main_command_refuses_production_mode() ! {
|
||||
compiler := v3_driver_test_executable()
|
||||
if !os.is_executable(os.join_path(os.dir(compiler), 'thirdparty', 'tcc', 'tcc.exe')) { return }
|
||||
root := os.join_path(os.vtmp_dir(), 'vup_tcc_optimization_${os.getpid()}')
|
||||
@@ -154,7 +154,14 @@ fn test_cached_tcc_main_command_preserves_production_optimization() ! {
|
||||
for name in ['VFLAGS', 'CFLAGS', 'LDFLAGS', 'V3_CACHE_REFRESH'] { os.unsetenv(name) }
|
||||
source := os.join_path(root, 'main.v')
|
||||
os.write_file(source, 'import math\nfn main() { println(math.abs(-3.0)) }\n')!
|
||||
for mode in [[]string{}, ['-prod'], ['-prod', '-no-prod-options']] {
|
||||
// TinyCC is not a -prod compiler, so the same build with -prod stops before compiling.
|
||||
for mode in [['-prod'], ['-prod', '-no-prod-options']] {
|
||||
refused := os.exec([compiler, '-cc', 'tcc', '-gc', 'none', '-showcc', ...mode, '-o',
|
||||
os.join_path(root, 'program'), source])
|
||||
assert refused.exit_code == 1, refused.output
|
||||
assert refused.output.contains('tcc is not allowed with -prod'), refused.output
|
||||
}
|
||||
for mode in [[]string{}] {
|
||||
result := os.exec([compiler, '-cc', 'tcc', '-gc', 'none', '-showcc', ...mode, '-o',
|
||||
os.join_path(root, 'program'), source])
|
||||
assert result.exit_code == 0, result.output
|
||||
@@ -553,7 +560,8 @@ fn test_v3_bundled_tcc_probe_eligibility() {
|
||||
host_target: windows_target
|
||||
target: windows_target
|
||||
})
|
||||
// An explicit `-cc tcc` still wins with -prod on Windows.
|
||||
// The probe still finds the bundled TinyCC for an explicit `-cc tcc` with -prod on Windows;
|
||||
// the driver then refuses that build (see v3_tcc_prod_rejected).
|
||||
assert v3_should_probe_bundled_tcc(V3BundledTccProbeOptions{
|
||||
...base
|
||||
is_prod: true
|
||||
@@ -565,6 +573,50 @@ fn test_v3_bundled_tcc_probe_eligibility() {
|
||||
})
|
||||
}
|
||||
|
||||
fn test_effective_c_compiler_name_does_not_read_tinycc_from_names_that_only_contain_tcc() {
|
||||
root := os.join_path(os.vtmp_dir(), 'v3_notcc_driver_${os.getpid()}')
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
// The V executable is a real compiler driver that is not TinyCC.
|
||||
not_tinycc := os.join_path(root, 'distcc.exe')
|
||||
os.cp(@VEXE, not_tinycc) or { panic(err) }
|
||||
assert effective_c_compiler_name(not_tinycc, pref.host_target()) != 'tinyc'
|
||||
}
|
||||
|
||||
fn test_v3_tcc_prod_rejected_only_for_a_prod_c_build_that_resolves_to_tinycc() {
|
||||
assert v3_tcc_prod_rejected(true, 'c', 'tinyc', 'tcc')
|
||||
assert !v3_tcc_prod_rejected(true, 'c', 'gcc', 'gcc')
|
||||
assert !v3_tcc_prod_rejected(true, 'c', 'clang', 'clang')
|
||||
assert !v3_tcc_prod_rejected(true, 'arm64', 'tinyc', 'tcc')
|
||||
assert !v3_tcc_prod_rejected(false, 'c', 'tinyc', 'tcc')
|
||||
}
|
||||
|
||||
fn test_v3_tcc_prod_rejected_checks_actual_banner_for_known_compiler_names() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'v3_prod_compiler_banner_${os.getpid()}')
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
for name in ['clang-alias', 'gcc-alias'] {
|
||||
alias := os.join_path(root, name)
|
||||
os.write_file(alias, '#!/bin/sh\nprintf "tcc version 0.9.27\\n"\n')!
|
||||
os.chmod(alias, 0o700)!
|
||||
effective := effective_c_compiler_name(alias, pref.host_target())
|
||||
assert effective != 'tinyc'
|
||||
assert v3_tcc_prod_rejected(true, 'c', effective, alias)
|
||||
assert !v3_tcc_prod_rejected(false, 'c', effective, alias)
|
||||
assert !v3_tcc_prod_rejected(true, 'arm64', effective, alias)
|
||||
}
|
||||
clang := os.join_path(root, 'cc')
|
||||
os.write_file(clang, '#!/bin/sh\nprintf "clang version 19.1.7\\nInstalledDir: /opt/tinycc-tcc version-compatible-sdk/bin\\n"\n')!
|
||||
os.chmod(clang, 0o700)!
|
||||
assert effective_c_compiler_name(clang, pref.host_target()) == 'clang'
|
||||
assert !v3_tcc_prod_rejected(true, 'c', 'clang', clang)
|
||||
}
|
||||
|
||||
fn test_v3_windows_prod_msvc_ready_needs_a_matching_developer_environment() {
|
||||
ready := V3WindowsProdToolchain{
|
||||
cl: 'C:/VS/bin/cl.exe'
|
||||
|
||||
+32
-2
@@ -6680,6 +6680,20 @@ fn incremental_top_level_nodes(a &flat.FlatAst) []bool {
|
||||
return result
|
||||
}
|
||||
|
||||
fn program_has_translated_module_attributes(a &flat.FlatAst) bool {
|
||||
for node in a.nodes {
|
||||
if node.kind != .directive || !node.value.starts_with('@attributes:') {
|
||||
continue
|
||||
}
|
||||
idx := node.value.all_after('@attributes:').int()
|
||||
if idx >= 0 && idx < a.nodes.len && a.nodes[idx].kind == .module_decl
|
||||
&& node.generic_params().any(it.all_before(':').trim_space() == 'translated') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn incremental_declaration_attribute_signatures(a &flat.FlatAst) map[int]string {
|
||||
mut result := map[int]string{}
|
||||
for node in a.nodes {
|
||||
@@ -7675,6 +7689,7 @@ fn promote_scoped_type_metadata(mut tc types.TypeChecker) {
|
||||
tc.enum_fields = clone_string_list_map(tc.enum_fields)
|
||||
tc.flag_enums = clone_string_bool_map(tc.flag_enums)
|
||||
tc.interface_names = clone_string_bool_map(tc.interface_names)
|
||||
tc.interface_generic_params = clone_string_list_map(tc.interface_generic_params)
|
||||
tc.interface_fields = clone_struct_field_map(tc.interface_fields)
|
||||
tc.interface_embeds = clone_string_list_map(tc.interface_embeds)
|
||||
tc.interface_abstract_methods = clone_string_list_map(tc.interface_abstract_methods)
|
||||
@@ -7970,7 +7985,7 @@ fn effective_c_compiler_name(compiler string, target pref.Target) string {
|
||||
compiler_path := os.find_abs_path_of_executable(compiler) or { compiler }
|
||||
resolved_path := os.real_path(compiler_path)
|
||||
name := os.file_name(resolved_path).to_lower_ascii()
|
||||
if name.contains('tcc') || name.contains('tinyc') {
|
||||
if pref.tinycc_file_name(name) {
|
||||
return 'tinyc'
|
||||
}
|
||||
if name.contains('clang') {
|
||||
@@ -7995,7 +8010,7 @@ fn effective_c_compiler_name(compiler string, target pref.Target) string {
|
||||
}
|
||||
compiler_result := cmdexec.run(compiler_path, ['--version'])
|
||||
version := compiler_result.output.to_lower_ascii()
|
||||
if version.contains('tiny c compiler') || version.contains('tcc version') {
|
||||
if pref.ccompiler_version_is_tinycc(version) {
|
||||
return 'tinyc'
|
||||
}
|
||||
if version.contains('clang') {
|
||||
@@ -8027,6 +8042,14 @@ fn v3_select_implicit_c_compiler(c_compiler string, c_compiler_explicit bool, im
|
||||
return c_compiler
|
||||
}
|
||||
|
||||
// v3_tcc_prod_rejected reports whether a `-prod` C build would compile with TinyCC.
|
||||
// Probe the actual compiler for production builds because its name may suggest Clang or GCC.
|
||||
// FastC refuses `-prod` on its own, earlier.
|
||||
fn v3_tcc_prod_rejected(is_prod bool, backend string, effective_c_compiler string, c_compiler string) bool {
|
||||
return is_prod && backend == 'c'
|
||||
&& (effective_c_compiler == 'tinyc' || pref.ccompiler_is_tinycc(c_compiler))
|
||||
}
|
||||
|
||||
// V3CCompilerSelection is the C compiler that receives a compilation's first
|
||||
// build attempt, and the name (`tinyc`, `gcc`, `clang`, ...) the generated code
|
||||
// and the `// vtest build:` facts describe it by.
|
||||
@@ -11437,6 +11460,10 @@ pub fn run(args []string) {
|
||||
c_compiler = selection.c_compiler
|
||||
use_implicit_tcc_semantics := selection.use_implicit_tcc_semantics
|
||||
effective_c_compiler := selection.effective_c_compiler
|
||||
if v3_tcc_prod_rejected(is_prod, backend, effective_c_compiler, c_compiler) {
|
||||
eprintln('tcc is not allowed with -prod')
|
||||
exit(1)
|
||||
}
|
||||
if effective_c_compiler == 'msvc' && target.os != 'windows' && !output_cross_c {
|
||||
eprintln('MSVC requires a Windows target; cannot compile target `${target.os}`')
|
||||
exit(1)
|
||||
@@ -12624,7 +12651,10 @@ pub fn run(args []string) {
|
||||
}
|
||||
use_macos_dev_program_cache := backend == 'c' && program_cache_enabled && !is_prod && !is_shared
|
||||
&& !is_selfhost && prefs.normalized_target_os() == 'macos'
|
||||
// Translated body edits can require new integer-formatting helpers and map
|
||||
// declarations that the prior prefix did not contain. Rebuild that prefix.
|
||||
incremental_cache_enabled := use_macos_dev_program_cache
|
||||
&& !translated_mode && !program_has_translated_module_attributes(a)
|
||||
&& os.getenv('V3_CACHE_DISABLE_INCREMENTAL') != '1'
|
||||
mut generic_cache_inputs_ready := false
|
||||
mut incremental_snapshot := V3IncrementalSnapshot{}
|
||||
|
||||
@@ -728,6 +728,38 @@ fn test_clone_after_transform_keeps_cached_vlib_flag_ownership() {
|
||||
assert flags == []
|
||||
}
|
||||
|
||||
fn test_scoped_type_metadata_preserves_generic_interface_parameters() {
|
||||
$if !prealloc {
|
||||
return
|
||||
}
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.interface_names['Store'] = true
|
||||
tc.fn_ret_types['Store[T].get'] = types.Type(types.int_)
|
||||
scope := prealloc_scope_begin_for_v3()
|
||||
tc.interface_generic_params['Store'.clone()] = ['T'.clone()]
|
||||
prealloc_scope_leave_for_v3(scope)
|
||||
promote_scoped_type_metadata(mut tc)
|
||||
params := tc.interface_generic_params['Store']
|
||||
assert !scoped_value_owned(scope, params.data)
|
||||
assert !scoped_value_owned(scope, params[0].str)
|
||||
prealloc_scope_free_for_v3(scope)
|
||||
key := tc.interface_method_signature_key('Store', 'get') or { panic('missing interface method') }
|
||||
assert key == 'Store[T].get'
|
||||
}
|
||||
|
||||
fn test_translated_module_attributes_are_detected_for_prefix_regeneration() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_node(flat.Node{ kind: .module_decl, value: 'main' })
|
||||
assert !program_has_translated_module_attributes(&a)
|
||||
a.add_node(flat.Node{
|
||||
kind: .directive
|
||||
value: '@attributes:${int(id)}'
|
||||
payload: flat.node_payload(['translated'])
|
||||
})
|
||||
assert program_has_translated_module_attributes(&a)
|
||||
}
|
||||
|
||||
fn test_scoped_ast_storage_copy_preserves_persistent_text_and_promotes_retiring_payloads() {
|
||||
$if !prealloc {
|
||||
return
|
||||
|
||||
@@ -407,6 +407,19 @@ fn (mut g FlatGen) finish_cache_declaration_demand(const_code string) {
|
||||
g.cache_collect_declaration_refs(payload)
|
||||
}
|
||||
mut seen := map[string]bool{}
|
||||
// Fixed-array typedefs are emitted even when their only use is an optional
|
||||
// payload or an alias. Their element layouts must survive declaration pruning.
|
||||
for _, info in g.collect_fixed_array_typedefs_needed() {
|
||||
g.cache_require_declaration_type(info.arr, mut seen)
|
||||
}
|
||||
// Shared wrappers store their payload by value, including wrappers collected
|
||||
// from fields that were only visited by compile-time reflection.
|
||||
old_module := g.tc.cur_module
|
||||
for _, info in g.shared_type_names {
|
||||
g.tc.cur_module = info.module
|
||||
g.cache_require_declaration_type(g.tc.parse_type(info.inner), mut seen)
|
||||
}
|
||||
g.tc.cur_module = old_module
|
||||
for typ in g.multi_return_types {
|
||||
g.cache_require_declaration_type(typ, mut seen)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,68 @@ module c
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_final_cached_signatures_declare_tuple_payloads_before_wrappers() {
|
||||
for is_result in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
fn_id := a.add_node(flat.Node{ kind: .fn_decl, value: 'pair' })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.structs['dep.Box'] = [types.StructField{
|
||||
name: 'child'
|
||||
typ: types.Struct{ name: 'dep.Child' }
|
||||
}]
|
||||
tc.structs['dep.Child'] = [types.StructField{
|
||||
name: 'value'
|
||||
typ: types.int_
|
||||
}]
|
||||
tc.structs['dep.Unused'] = []types.StructField{}
|
||||
payload := types.Type(types.MultiReturn{
|
||||
types: [types.Type(types.Struct{ name: 'dep.Box' }), types.Type(types.string_)]
|
||||
})
|
||||
tc.fn_ret_types['dep.pair'] = if is_result {
|
||||
types.Type(types.ResultType{ base_type: payload })
|
||||
} else {
|
||||
types.Type(types.OptionType{ base_type: payload })
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cache_decl_demand = true
|
||||
g.decl_types_ready = true
|
||||
g.optional_types_ready = true
|
||||
g.multi_return_types_ready = true
|
||||
g.fn_gen_items = [
|
||||
FlatFnGenItem{ node_id: fn_id, module: 'dep', file: 'dep.v', c_name: 'dep__pair' },
|
||||
]
|
||||
g.gen_type_declaration_block()
|
||||
output := g.sb.str()
|
||||
child_position := output.index('struct dep__Child {') or { -1 }
|
||||
box_position := output.index('struct dep__Box {') or { -1 }
|
||||
tuple_position := output.index('struct multi_return_dep__Box_string {') or { -1 }
|
||||
wrapper := if is_result { '__v_result' } else { '__v_option' }
|
||||
wrapper_position := output.index('typedef struct ${wrapper}_multi_return_dep__Box_string') or {
|
||||
-1
|
||||
}
|
||||
assert child_position >= 0, output
|
||||
assert box_position > child_position, output
|
||||
assert tuple_position > box_position, output
|
||||
assert wrapper_position > tuple_position, output
|
||||
assert !output.contains('dep__Unused'), output
|
||||
}
|
||||
}
|
||||
|
||||
fn test_cached_header_retains_its_vlib_source_classification() {
|
||||
mut a := flat.FlatAst.new()
|
||||
a.cached_header_sources['/cache/builtin.vh'] = '/repo/vlib/builtin/array.v'
|
||||
a.cached_header_sources['/cache/user.vh'] = '/repo/project/user.v'
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.compiler_vroot = '/repo'
|
||||
assert g.c_source_file_is_in_vlib('/cache/builtin.vh')
|
||||
assert g.c_source_file_is_in_vlib('/repo/vlib/builtin/array.v')
|
||||
assert !g.c_source_file_is_in_vlib('/cache/user.vh')
|
||||
assert !g.c_source_file_is_in_vlib('/repo/project/user.v')
|
||||
}
|
||||
|
||||
fn test_cache_declaration_references_ignore_literals_and_comments() {
|
||||
mut g := FlatGen.new()
|
||||
g.cache_collect_declaration_refs('needed("quoted \\\" ignored", \'x\'); /* hidden */ // hidden_too\nother_2;')
|
||||
@@ -47,6 +109,70 @@ fn test_cache_declarations_include_nested_payloads_and_recursive_fields() {
|
||||
assert 'sample.Unused' !in names
|
||||
}
|
||||
|
||||
fn test_cache_demand_keeps_fixed_array_alias_element_layouts() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.structs['sample.Box'] = []types.StructField{}
|
||||
tc.structs['sample.Unused'] = []types.StructField{}
|
||||
tc.type_aliases['sample.Boxes'] = '[2]sample.Box'
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cache_decl_demand = true
|
||||
g.finish_cache_declaration_demand('')
|
||||
names := g.c_struct_decl_names()
|
||||
assert 'sample.Box' in names, names.str()
|
||||
assert 'sample.Unused' !in names, names.str()
|
||||
}
|
||||
|
||||
fn test_cache_demand_keeps_shared_wrapper_payload_layouts() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.structs['sample.Box'] = []types.StructField{}
|
||||
tc.struct_modules['sample.Box'] = 'sample'
|
||||
tc.structs['sample.Unused'] = []types.StructField{}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cache_decl_demand = true
|
||||
g.register_shared_type_name('sample.Box', 'sample')
|
||||
g.finish_cache_declaration_demand('')
|
||||
names := g.c_struct_decl_names()
|
||||
assert 'sample.Box' in names, names.str()
|
||||
assert 'sample.Unused' !in names, names.str()
|
||||
}
|
||||
|
||||
fn test_cache_map_key_helpers_follow_their_builtin_prototypes() {
|
||||
mut a := flat.FlatAst.new()
|
||||
file_id := a.add_val(.file, 'builtin.vh')
|
||||
module_id := a.add_val(.module_decl, 'builtin')
|
||||
fn_id := a.add_node(flat.Node{
|
||||
kind: .fn_decl
|
||||
value: 'fast_string_eq'
|
||||
typ: 'bool'
|
||||
is_mut: true
|
||||
})
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.fn_ret_types['fast_string_eq'] = types.bool_
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.top_level_node_ids = [int(file_id), int(module_id), int(fn_id)]
|
||||
g.cache_decl_demand = true
|
||||
g.fixed_array_map_key_types['Array_fixed_string_2'] = types.ArrayFixed{
|
||||
elem_type: types.Type(types.string_)
|
||||
len: 2
|
||||
}
|
||||
g.gen_pre_body_support_declarations()
|
||||
source := g.sb.str()
|
||||
prototype := source.index('bool fast_string_eq(') or { panic(source) }
|
||||
definition := source.index('static u64 Array_fixed_string_2_map_key_hash(void* pkey) {') or {
|
||||
panic(source)
|
||||
}
|
||||
assert prototype < definition, source
|
||||
assert source.contains('fast_string_eq((*left)[i], (*right)[i])'), source
|
||||
}
|
||||
|
||||
fn test_cache_signature_collection_omits_unused_function_payloads() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
|
||||
+27
-2
@@ -4038,9 +4038,19 @@ fn (mut g FlatGen) write_parallel_cc_unit_marker() {
|
||||
|
||||
fn (mut g FlatGen) gen_pre_body_support_declarations() {
|
||||
g.fixed_array_map_key_forward_decls()
|
||||
support_start := g.sb.len
|
||||
g.fixed_array_map_key_definitions()
|
||||
g.gen_ownership_recursive_drop_helpers()
|
||||
g.cached_header_forward_decls()
|
||||
if g.cache_decl_demand {
|
||||
// Support helpers can be the only callers of cached builtin functions.
|
||||
// Select and emit those prototypes before the helper definitions.
|
||||
support := g.sb.cut_to(support_start)
|
||||
g.cache_collect_declaration_refs(support)
|
||||
g.cached_header_forward_decls()
|
||||
g.write(support)
|
||||
} else {
|
||||
g.cached_header_forward_decls()
|
||||
}
|
||||
g.interface_method_forward_decls()
|
||||
g.shared_dup_fns()
|
||||
if !g.skip_enum_autostr {
|
||||
@@ -4135,6 +4145,15 @@ fn (mut g FlatGen) gen_global_declaration_block() {
|
||||
// immutable after cgen preparation. Parallel self-host cgen can build it on the
|
||||
// caller while the persistent worker pool emits function bodies.
|
||||
fn (mut g FlatGen) gen_type_declaration_block() {
|
||||
// The final function selection can add wrapped tuple signatures after cache
|
||||
// demand collection. Discover them before emitting their payload layouts.
|
||||
g.collect_selected_declaration_signature_types()
|
||||
if g.cache_decl_demand {
|
||||
mut seen := map[string]bool{}
|
||||
for typ in g.multi_return_types {
|
||||
g.cache_require_declaration_type(typ, mut seen)
|
||||
}
|
||||
}
|
||||
g.enum_decls()
|
||||
g.type_forward_decls()
|
||||
g.type_alias_decls(false)
|
||||
@@ -5516,7 +5535,12 @@ fn (g &FlatGen) c_source_file_is_in_vlib(source_file string) bool {
|
||||
if root.len == 0 {
|
||||
return false
|
||||
}
|
||||
return source_file.replace('\\', '/').starts_with('${root}/vlib/')
|
||||
original_file := if !isnil(g.a) {
|
||||
g.a.cached_header_sources[source_file] or { source_file }
|
||||
} else {
|
||||
source_file
|
||||
}
|
||||
return original_file.replace('\\', '/').starts_with('${root}/vlib/')
|
||||
}
|
||||
|
||||
// note_c_include_directive records that a file (and, for user code, its module)
|
||||
@@ -19251,6 +19275,7 @@ fn (mut g FlatGen) portable_native_runtime_helpers() {
|
||||
.replace('defined(V_GCBOEHM_NATIVE)', 'defined(CUSTOM_DEFINE_gcboehm)')
|
||||
.replace('#include <gc.h>', '#include <gc/gc.h>')
|
||||
.replace('#include "segfault_handler_nix.h"', signal_header)
|
||||
.replace('#define V_SEGFAULT_HANDLER_SCOPE\n', '#define V_SEGFAULT_HANDLER_SCOPE static\n')
|
||||
.replace('void* v_thread_', 'static void* v_thread_')
|
||||
.replace('void v_thread_', 'static void v_thread_'))
|
||||
g.writeln('#endif')
|
||||
|
||||
@@ -39,7 +39,9 @@ fn main() {
|
||||
mut direct := log.ThreadSafeLog{}
|
||||
assert direct.get_level() == .debug
|
||||
log.use_stdout()
|
||||
assert log.get_level() == .debug
|
||||
slevel := \$d("log_default_level", "info")
|
||||
expected := log.level_from_tag(slevel.to_upper()) or { panic("invalid log level") }
|
||||
assert log.get_level() == expected
|
||||
log.info("logger-defaults-ok")
|
||||
}
|
||||
'
|
||||
|
||||
@@ -57,6 +57,14 @@ fn main() {
|
||||
}
|
||||
|
||||
')!
|
||||
// Cached cross builds also link native helper objects; the embedded installer
|
||||
// must not export a second copy of the native signal-handler symbol.
|
||||
for _ in 0 .. 2 {
|
||||
cached := cmdexec.run('env', ['V3CACHE=${os.join_path(root, 'cache')}', @VEXE, '-cc', cc,
|
||||
'-gc', 'none', '-prealloc', '-os', 'cross', 'run', source])
|
||||
assert cached.exit_code == 0, cached.output
|
||||
assert cached.output.trim_space() == 'portable', cached.output
|
||||
}
|
||||
generated := cmdexec.run(@VEXE, ['-gc', 'none', '-prealloc', '-nocache', '-os', 'cross', '-o',
|
||||
c_source, source])
|
||||
assert generated.exit_code == 0, generated.output
|
||||
|
||||
+18
-12
@@ -7572,26 +7572,32 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
|
||||
}
|
||||
if node.typ.len > 0 {
|
||||
decl_type := g.tc.parse_resolution_type(node.typ)
|
||||
if _ := array_like_type(decl_type) {
|
||||
rhs_v_type = decl_type
|
||||
} else if _ := array_fixed_type(decl_type) {
|
||||
rhs_v_type = decl_type
|
||||
if !g.type_contains_generic_placeholder(decl_type) {
|
||||
if _ := array_like_type(decl_type) {
|
||||
rhs_v_type = decl_type
|
||||
} else if _ := array_fixed_type(decl_type) {
|
||||
rhs_v_type = decl_type
|
||||
}
|
||||
}
|
||||
}
|
||||
if lhs.typ.len > 0 {
|
||||
lhs_decl_type := g.parse_node_type(&lhs)
|
||||
if _ := array_like_type(lhs_decl_type) {
|
||||
rhs_v_type = lhs_decl_type
|
||||
} else if _ := array_fixed_type(lhs_decl_type) {
|
||||
rhs_v_type = lhs_decl_type
|
||||
if !g.type_contains_generic_placeholder(lhs_decl_type) {
|
||||
if _ := array_like_type(lhs_decl_type) {
|
||||
rhs_v_type = lhs_decl_type
|
||||
} else if _ := array_fixed_type(lhs_decl_type) {
|
||||
rhs_v_type = lhs_decl_type
|
||||
}
|
||||
}
|
||||
}
|
||||
if lhs.kind == .ident {
|
||||
lhs_type := g.usable_expr_type(lhs_id)
|
||||
if _ := array_like_type(lhs_type) {
|
||||
rhs_v_type = lhs_type
|
||||
} else if _ := array_fixed_type(lhs_type) {
|
||||
rhs_v_type = lhs_type
|
||||
if !g.type_contains_generic_placeholder(lhs_type) {
|
||||
if _ := array_like_type(lhs_type) {
|
||||
rhs_v_type = lhs_type
|
||||
} else if _ := array_fixed_type(lhs_type) {
|
||||
rhs_v_type = lhs_type
|
||||
}
|
||||
}
|
||||
}
|
||||
if arr := array_like_type(rhs_v_type) {
|
||||
|
||||
@@ -1834,6 +1834,10 @@ fn generate_source_pieces(input_sources []FastcSourceFile, module_aliases map[st
|
||||
mut global_sources := map[string]string{}
|
||||
generic_method_sources := fastc_collect_generic_and_declaration_indexes(mut sources, prefs, mut declared_types, mut declared_kinds, mut enum_flags, mut params_structs, mut type_source_paths, mut type_sources, mut constants, mut public_constants, mut constant_sources, mut constant_spans, mut global_sources, mut globals, mut public_globals)!
|
||||
timer.mark('declaration_indexes')
|
||||
// builtin declares IError as `pub struct C.IError`, which the index does not record, so
|
||||
// signatures such as `fn f() IError` in selfhost sources would be undeclared. The arm64 path
|
||||
// records the same intrinsic error interface.
|
||||
declared_types['IError'] = true
|
||||
// The type declarations depend only on the declaration index, so they are
|
||||
// rendered on a worker while the signatures are collected below; the
|
||||
// composite typedefs that precede them need both and are rendered after.
|
||||
|
||||
@@ -141,6 +141,8 @@ NB: the build flags are shared with the run command too:
|
||||
Compile the executable in production mode, where most optimizations are enabled.
|
||||
Note that most V warnings turn to errors, if you pass -prod, so you will have
|
||||
to fix them first.
|
||||
TinyCC is not a -prod compiler: `-cc tcc`, or any compiler that resolves to TinyCC,
|
||||
stops with `tcc is not allowed with -prod`. `-b fastc` is not supported with `-prod` either.
|
||||
Note: gcc versions > 12.0 often have issues with inlining, that manifest with `-prod`.
|
||||
To work around them, you can use `-prod -cflags -fno-inline-small-functions` .
|
||||
|
||||
@@ -377,3 +379,6 @@ For WebAssembly-specific build flags, use `v help build-wasm`.
|
||||
|
||||
See also:
|
||||
`v help run` for documentation regarding `v run`.
|
||||
|
||||
Production C builds reject TinyCC, including executable aliases. This restriction does not
|
||||
apply to help/version commands or non-C backends.
|
||||
@@ -36,8 +36,9 @@ Ownership dispatch preserves V3_PRIMARY_VEXE independently of the helper's VEXE.
|
||||
Each module being cached is printed to stdout as `Caching module <name>...`.
|
||||
Before each refresh build, progress shows the cache record index and total,
|
||||
normal or production mode, program or test mode, and its original compiler flags.
|
||||
For example: `> Refreshing module cache [1/4] (production tests; -cc tcc -prod).`
|
||||
Equivalent builds and unavailable inputs are skipped, so progress indices can jump.
|
||||
For example: `> Refreshing module cache [1/2] (normal program; -cc tcc).`
|
||||
Equivalent builds, unavailable inputs and production TinyCC builds are skipped, so progress
|
||||
indices can jump. TinyCC is not a `-prod` compiler.
|
||||
The final summary reports how many distinct build configurations were checked.
|
||||
The same module can be cached separately for different TinyCC build modes.
|
||||
This also runs when the compiler itself is already updated. A cache refresh
|
||||
|
||||
@@ -579,7 +579,7 @@ fn main() {
|
||||
assert compilation.output.contains('VirtualProtect(ptr, size, PAGE_READWRITE, &tmp);')
|
||||
}
|
||||
|
||||
fn test_windows_tcc_boehm_prod_does_not_emit_gc_remove_roots() {
|
||||
fn test_windows_tcc_prod_is_refused_before_c_output() {
|
||||
os.chdir(vroot) or {}
|
||||
cc := windows_tcc_ccompiler_for_coutput_test()
|
||||
if cc == '' {
|
||||
@@ -601,10 +601,10 @@ fn main() {
|
||||
defer {
|
||||
os.rm(test_source) or {}
|
||||
}
|
||||
cmd := '${os.quoted_path(vexe)} -o - -os windows -cc ${cc} -prod ${os.quoted_path(test_source)}'
|
||||
// TinyCC is not a -prod compiler, so the compilation stops before it emits any C.
|
||||
compilation := os.exec([vexe, '-o', '-', '-os', 'windows', '-cc', cc, '-prod', test_source])
|
||||
ensure_compilation_succeeded(compilation, cmd)
|
||||
assert !compilation.output.contains('GC_remove_roots(')
|
||||
assert compilation.exit_code != 0, compilation.output
|
||||
assert compilation.output.contains('tcc is not allowed with -prod'), compilation.output
|
||||
}
|
||||
|
||||
fn windows_tcc_ccompiler_for_coutput_test() string {
|
||||
|
||||
@@ -1,9 +1,63 @@
|
||||
module markused
|
||||
|
||||
import os
|
||||
import v.flat
|
||||
import v.parser
|
||||
import v.pref
|
||||
import v.types
|
||||
import v.token
|
||||
|
||||
fn test_warm_cache_keeps_ierror_methods_and_their_helpers() {
|
||||
root := os.join_path(os.vtmp_dir(), 'cached_ierror_${os.getpid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
builtin_file := os.join_path(root, 'builtin.vh')
|
||||
error_file := os.join_path(root, 'errors.v')
|
||||
main_file := os.join_path(root, 'main.v')
|
||||
os.write_file(builtin_file, 'module builtin\npub interface IError {\nmsg() string\ncode() int\n}\n')!
|
||||
os.write_file(error_file, 'module errors
|
||||
pub struct TestError {}
|
||||
fn message_helper() string { return "message" }
|
||||
fn code_helper() int { return 7 }
|
||||
fn (e TestError) msg() string { return message_helper() }
|
||||
fn (e TestError) code() int { return code_helper() }
|
||||
fn unused_helper() {}
|
||||
')!
|
||||
os.write_file(main_file, 'import errors\nfn main() { _ := errors.TestError{} }\n')!
|
||||
mut p := parser.Parser.new(pref.new_preferences())
|
||||
a := p.parse_files([builtin_file, error_file, main_file])
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.check_semantics()
|
||||
assert tc.errors.len == 0, tc.errors.str()
|
||||
used := mark_used_for_cache(a, &tc, []string{}, map[string]bool{})
|
||||
assert used['errors.TestError.msg']
|
||||
assert used['errors.TestError.code']
|
||||
assert used['errors.message_helper']
|
||||
assert used['errors.code_helper']
|
||||
assert !used['errors.unused_helper']
|
||||
}
|
||||
|
||||
fn test_all_functions_keeps_generic_calls_outside_literal_main() {
|
||||
root := os.join_path(os.vtmp_dir(), 'all_fn_literal_${os.getpid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
builtin_file := os.join_path(root, 'builtin.v')
|
||||
main_file := os.join_path(root, 'main.v')
|
||||
os.write_file(builtin_file, 'module builtin\nfn generic_helper[T](value T) {}\nfn wrapper() { generic_helper(1) }\nfn println(value string) {}\n')!
|
||||
os.write_file(main_file, 'fn main() { println("literal") }\n')!
|
||||
mut p := parser.Parser.new(pref.new_preferences())
|
||||
a := p.parse_files([builtin_file, main_file])
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.diagnostic_files[main_file] = true
|
||||
assert is_trivial_literal_output_program(a, tc.diagnostic_files)
|
||||
used, uses_generics := mark_all_used_with_generic_usage(a, &tc, []string{})
|
||||
assert uses_generics
|
||||
assert used['wrapper']
|
||||
assert used['generic_helper']
|
||||
}
|
||||
|
||||
fn test_collection_alias_methods_keep_declared_and_inherited_precedence() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
|
||||
@@ -280,7 +280,8 @@ pub fn reachable_const_exprs(a &flat.FlatAst, tc &types.TypeChecker, root_ids []
|
||||
fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files map[string]bool, cache_modules map[string]bool, cache_mode bool, detect_generics bool, all_functions bool, allow_trivial_literal_output bool, prepared &PreparedMarkusedDecls, library_body_checker voidptr, library_check_parallel bool) (map[string]bool, bool) {
|
||||
mut mu_sw := time.new_stopwatch()
|
||||
trivial_literal_output := allow_trivial_literal_output && !cache_mode && cache_modules.len == 0
|
||||
&& test_files.len == 0 && is_trivial_literal_output_program(a, tc.diagnostic_files)
|
||||
&& test_files.len == 0 && !all_functions
|
||||
&& is_trivial_literal_output_program(a, tc.diagnostic_files)
|
||||
// An exact literal-output program has no user expressions or declarations that
|
||||
// can instantiate a generic. Skip generic indexes and per-node generic checks,
|
||||
// just as the known non-generic self-host path does.
|
||||
@@ -551,6 +552,18 @@ fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files
|
||||
}
|
||||
queue << 'main'
|
||||
used['main'] = true
|
||||
if cache_mode {
|
||||
// Cached IError dispatchers name every implementer's methods, even when
|
||||
// their callers have no body in the warm AST. Cgen emits these methods,
|
||||
// so retain their transformations and the helpers their bodies call.
|
||||
for impl in tc.ierror_impl_names() {
|
||||
for method in ['msg', 'code'] {
|
||||
for alias in interface_implementer_method_aliases(impl, method, tc) {
|
||||
enqueue(alias, mut used, mut queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$if ownership ? {
|
||||
// Ownership cleanup and clone lowering emits direct C references to these
|
||||
// process-wide IError sentinels. They have no source-AST reference for the
|
||||
|
||||
@@ -29,3 +29,209 @@ fn test_ccompiler_can_assemble_rejects_a_tcc_alias() {
|
||||
found := find_system_assembler() or { panic('the fake Clang was not found') }
|
||||
assert found == alias_path
|
||||
}
|
||||
|
||||
fn test_tcc_with_prod_requested_needs_both_prod_and_a_tinycc_name() {
|
||||
assert tcc_with_prod_requested(['-prod', '-cc', 'tcc', 'main.v'])
|
||||
assert tcc_with_prod_requested(['-cc', 'C:/tools/tcc.exe', '-prod', 'main.v'])
|
||||
assert tcc_with_prod_requested(['-cc=tinyc', '-prod', 'main.v'])
|
||||
assert tcc_with_prod_requested(['-prod', '-cc', 'TCC.EXE', 'main.v'])
|
||||
assert !tcc_with_prod_requested(['-cc', 'tcc', 'main.v'])
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', 'clang', 'main.v'])
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc'])
|
||||
assert !tcc_with_prod_requested(['-prod', 'main.v'])
|
||||
// The last `-cc` wins, so a `VFLAGS=-cc tcc` can be overridden on the command line.
|
||||
assert !tcc_with_prod_requested(['-cc', 'tcc', '-prod', '-cc', 'gcc', 'main.v'])
|
||||
assert tcc_with_prod_requested(['-cc', 'gcc', '-prod', '-cc', 'tcc', 'main.v'])
|
||||
}
|
||||
|
||||
fn test_tcc_prod_detection_does_not_match_unrelated_names_or_backends() {
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', 'distcc'])
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', 'notcc'])
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', 'tcc', '-b', 'arm64'])
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', 'tcc', '-b', 'fastc'])
|
||||
assert tcc_with_prod_requested(['-prod', '-cc', 'tcc', '-b', 'arm64', '-b', 'c'])
|
||||
assert !tcc_with_prod_requested(['-cc', '-prod'])
|
||||
}
|
||||
|
||||
fn test_tcc_prod_detection_checks_compiler_aliases() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'v_pref_prod_alias_${os.getpid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
alias := os.join_path(root, 'cc')
|
||||
os.write_file(alias, '#!/bin/sh\nprintf "tcc version 0.9.27\\n"\n')!
|
||||
os.chmod(alias, 0o700)!
|
||||
assert tcc_with_prod_requested(['-prod', '-cc', alias])
|
||||
os.write_file(alias, '#!/bin/sh\nprintf "clang version 18.0.0\\n"\n')!
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', alias])
|
||||
os.write_file(alias, '#!/bin/sh\nprintf "clang version 19.1.7\\nInstalledDir: /opt/tinycc-tcc version-compatible-sdk/bin\\n"\n')!
|
||||
assert !ccompiler_is_tinycc(alias)
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', alias])
|
||||
assert ccompiler_can_assemble(alias)
|
||||
}
|
||||
|
||||
fn test_ccompiler_version_is_tinycc_recognizes_banner_lines_only() {
|
||||
for banner in ['tcc version 0.9.27 (arm64 Darwin)', 'TinyCC version 0.9.27',
|
||||
'Tiny C Compiler 0.9.27', 'Tiny C Compiler - Copyright (C) 2001-2026',
|
||||
'warning: wrapper\n tcc version 0.9.27\n', 'TinyCC'] {
|
||||
assert ccompiler_version_is_tinycc(banner), banner
|
||||
}
|
||||
for version in ['clang version 19.1.7\nInstalledDir: /opt/tinycc-compatible-sdk/bin',
|
||||
'clang version 19.1.7\nInstalledDir: /opt/tcc version-compatible-sdk/bin',
|
||||
'gcc version 14.0\nConfigured with: --prefix=/opt/tiny c compiler-sdk',
|
||||
'clang version 19.1.7 (supports TinyCC-compatible inputs)', 'notcc version 1.0'] {
|
||||
assert !ccompiler_version_is_tinycc(version), version
|
||||
}
|
||||
}
|
||||
|
||||
fn test_tinycc_file_name_matches_tcc_names_only() {
|
||||
for name in ['tcc', 'tcc.exe', 'TCC.EXE', 'tinyc', 'tinycc', 'x86_64-w64-mingw32-tcc'] {
|
||||
assert tinycc_file_name(name), name
|
||||
}
|
||||
for name in ['distcc', 'notcc', 'notcc.exe', 'gcc', 'clang', 'cc'] {
|
||||
assert !tinycc_file_name(name), name
|
||||
}
|
||||
}
|
||||
|
||||
fn test_tcc_with_prod_requested_finds_tinycc_under_another_name() {
|
||||
bundled := os.join_path(@VEXEROOT, 'thirdparty', 'tcc', 'tcc.exe')
|
||||
if !os.is_file(bundled) {
|
||||
return
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'v_pref_tinycc_alias_${os.getpid()}')
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
alias_path := os.join_path(root, 'cc_alias.exe')
|
||||
os.cp(bundled, alias_path) or { panic(err) }
|
||||
assert tcc_with_prod_requested(['-prod', '-cc', alias_path])
|
||||
assert !tcc_with_prod_requested(['-cc', alias_path])
|
||||
}
|
||||
|
||||
fn test_tcc_with_prod_requested_ignores_other_executables_with_tcc_in_their_name() {
|
||||
root := os.join_path(os.vtmp_dir(), 'v_pref_notcc_${os.getpid()}')
|
||||
os.mkdir_all(root) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
// The V executable is a real compiler driver that is not TinyCC.
|
||||
not_tinycc := os.join_path(root, 'notcc.exe')
|
||||
os.cp(@VEXE, not_tinycc) or { panic(err) }
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', not_tinycc])
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', 'distcc'])
|
||||
}
|
||||
|
||||
fn test_tcc_prod_detection_skips_option_values() {
|
||||
assert !tcc_with_prod_requested(['-cc', 'tcc', '-o', '-prod', 'main.v'])
|
||||
assert !tcc_with_prod_requested(['-prod', '-cc', 'clang', '-o', '-cc=tcc', 'main.v'])
|
||||
assert tcc_with_prod_requested(['-prod', '-cc', 'tcc', '-o', '-b=arm64', 'main.v'])
|
||||
assert tcc_with_prod_requested(['-profile', '-prod', '-cc', 'tcc', 'main.v'])
|
||||
assert tcc_with_prod_requested(['-profile', 'report.txt', '-prod', '-cc', 'tcc', 'main.v'])
|
||||
}
|
||||
|
||||
fn test_tcc_prod_detection_uses_recorded_wrapper_directory_without_chdir() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'v_pref_cwd_${os.getpid()}')
|
||||
project := os.join_path(root, 'tinycc-project')
|
||||
os.mkdir_all(project)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
wrapper := os.join_path(root, 'wrapper')
|
||||
os.write_file(wrapper, '#!/bin/sh\ncase "$PWD" in\n*/tinycc-project) echo "tcc version 0.9.27" ;;\n*) echo "clang version 19.1.7" ;;\nesac\n')!
|
||||
os.chmod(wrapper, 0o700)!
|
||||
before := os.getwd()
|
||||
for args in [['-prod', '-cc', wrapper], ['-prod', '-cc', '../wrapper'],
|
||||
['-prod', '-cc=../wrapper']] {
|
||||
assert tcc_with_prod_requested_in(args, project)
|
||||
}
|
||||
assert !tcc_with_prod_requested_in(['-prod', '-cc', wrapper], root)
|
||||
assert !tcc_with_prod_requested_in(['-cc', wrapper], project)
|
||||
assert !tcc_with_prod_requested_in(['-cc', wrapper, '-o', '-prod'], project)
|
||||
assert os.getwd() == before
|
||||
bare := 'pref_context_cc_${os.getpid()}'
|
||||
os.cp(wrapper, os.join_path(project, bare))!
|
||||
os.chmod(os.join_path(project, bare), 0o700)!
|
||||
saved_path := os.getenv('PATH')
|
||||
defer { os.setenv('PATH', saved_path, true) }
|
||||
for prefix in ['.', ''] {
|
||||
os.setenv('PATH', prefix + os.path_delimiter + saved_path, true)
|
||||
assert tcc_with_prod_requested_in(['-prod', '-cc', bare], project)
|
||||
assert !tcc_with_prod_requested_in(['-prod', '-cc', bare], root)
|
||||
assert os.getenv('PATH') == prefix + os.path_delimiter + saved_path
|
||||
}
|
||||
os.mkdir_all(os.join_path(project, 'bin'))!
|
||||
os.mv(os.join_path(project, bare), os.join_path(project, 'bin', bare))!
|
||||
os.setenv('PATH', 'bin' + os.path_delimiter + saved_path, true)
|
||||
assert tcc_with_prod_requested_in(['-prod', '-cc', bare], project)
|
||||
assert os.getwd() == before
|
||||
}
|
||||
|
||||
fn test_tcc_prod_detection_prefers_launchable_local_compiler_before_path() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'v_pref_local_cc_${os.getpid()}')
|
||||
project := os.join_path(root, 'project')
|
||||
bin := os.join_path(root, 'bin')
|
||||
os.mkdir_all(project)!
|
||||
os.mkdir_all(bin)!
|
||||
saved_path := os.getenv('PATH')
|
||||
defer {
|
||||
os.setenv('PATH', saved_path, true)
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
local := os.join_path(project, 'clang')
|
||||
search := os.join_path(bin, 'clang')
|
||||
os.write_file(local, '#!/bin/sh\necho "tcc version 0.9.27"\n')!
|
||||
os.write_file(search, '#!/bin/sh\necho "clang version 19.1.7"\n')!
|
||||
os.chmod(local, 0o700)!
|
||||
os.chmod(search, 0o700)!
|
||||
os.setenv('PATH', bin + os.path_delimiter + saved_path, true)
|
||||
before := os.getwd()
|
||||
assert ccompiler_path_in('clang', project) == local
|
||||
assert tcc_with_prod_requested_in(['-prod', '-cc', 'clang'], project)
|
||||
assert !tcc_with_prod_requested_in(['-prod', '-cc', 'clang'], root)
|
||||
os.write_file(local, '#!/bin/sh\necho "clang version 19.1.7"\n')!
|
||||
os.write_file(search, '#!/bin/sh\necho "tcc version 0.9.27"\n')!
|
||||
assert !tcc_with_prod_requested_in(['-prod', '-cc', 'clang'], project)
|
||||
assert tcc_with_prod_requested_in(['-prod', '-cc', 'clang'], root)
|
||||
assert os.getwd() == before
|
||||
}
|
||||
|
||||
fn test_ccompiler_windows_explicit_path_uses_exe_not_com_or_cmd() {
|
||||
root := os.join_path(os.vtmp_dir(), 'v_pref_windows_paths_${os.getpid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
path := os.join_path(root, 'clang')
|
||||
for suffix in ['', '.com', '.exe', '.cmd', '.bin'] {
|
||||
os.write_file(path + suffix, 'dummy executable-path fixture')!
|
||||
os.chmod(path + suffix, if suffix in ['.com', '.exe', '.cmd'] { 0o700 } else { 0o600 })!
|
||||
}
|
||||
assert ccompiler_windows_executable_path(path) == path + '.exe'
|
||||
assert ccompiler_windows_executable_path(path + '.bin') == path + '.bin'
|
||||
os.rm(path + '.exe')!
|
||||
assert ccompiler_windows_executable_path(path) == path
|
||||
os.rm(path)!
|
||||
assert ccompiler_windows_executable_path(path) == path
|
||||
$if windows {
|
||||
assert os.real_path(ccompiler_path_in('./clang.bin', root)) == os.real_path(path + '.bin')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_ccompiler_windows_percent_expansion_matches_launch_spelling() {
|
||||
name := 'V_PREF_COMPILER_SDK_${os.getpid()}'
|
||||
missing := 'V_PREF_COMPILER_UNSET_${os.getpid()}'
|
||||
previous := os.getenv(name)
|
||||
defer {
|
||||
if previous == '' { os.unsetenv(name) } else { os.setenv(name, previous, true) }
|
||||
}
|
||||
os.setenv(name, 'C:/compiler sdk', true)
|
||||
assert ccompiler_windows_expand_env_vars('%${name}%/clang.exe') == 'C:/compiler sdk/clang.exe'
|
||||
assert ccompiler_windows_expand_env_vars('%${missing}%/clang.exe') == '%${missing}%/clang.exe'
|
||||
assert ccompiler_windows_expand_env_vars('%%/clang.exe') == '%%/clang.exe'
|
||||
assert ccompiler_windows_expand_env_vars('%${name}/clang.exe') == '%${name}/clang.exe'
|
||||
}
|
||||
+191
-3
@@ -1,6 +1,7 @@
|
||||
module pref
|
||||
|
||||
import os
|
||||
import strings
|
||||
import time
|
||||
import v.cmdexec
|
||||
|
||||
@@ -1195,7 +1196,7 @@ pub fn normalized_arch(target_arch string) string {
|
||||
// out an alias for tcc.
|
||||
pub fn ccompiler_can_assemble(ccompiler string) bool {
|
||||
real_name := os.file_name(os.real_path(ccompiler)).to_lower_ascii()
|
||||
if real_name.contains('tcc') || real_name.contains('tinyc') || real_name.contains('msvc')
|
||||
if tinycc_file_name(real_name) || real_name.contains('msvc')
|
||||
|| real_name in ['cl', 'cl.exe'] {
|
||||
return false
|
||||
}
|
||||
@@ -1203,8 +1204,7 @@ pub fn ccompiler_can_assemble(ccompiler string) bool {
|
||||
for version_flag in ['--version', '-v'] {
|
||||
res := os.exec([ccompiler, '${version_flag}'])
|
||||
output := res.output.to_lower_ascii()
|
||||
if output.contains('tiny c compiler') || output.contains('tinycc')
|
||||
|| output.contains('\ntcc') || output.starts_with('tcc')
|
||||
if ccompiler_version_is_tinycc(output)
|
||||
|| output.contains('microsoft c/c++') || output.contains('msvc') {
|
||||
return false
|
||||
}
|
||||
@@ -1216,6 +1216,194 @@ pub fn ccompiler_can_assemble(ccompiler string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// tcc_with_prod_requested reports whether a production C build selects TinyCC.
|
||||
// Compiler option values are skipped and the last compiler/backend options win.
|
||||
// Executable aliases follow ccompiler_is_tinycc in the caller's working directory.
|
||||
pub fn tcc_with_prod_requested(args []string) bool {
|
||||
return tcc_with_prod_requested_in(args, '')
|
||||
}
|
||||
|
||||
// tcc_with_prod_requested_in reports whether a production C build selects TinyCC in cwd.
|
||||
// Relative compiler paths and wrapper version queries use cwd without changing the caller's
|
||||
// working directory. Compiler option values are skipped and the last compiler/backend win.
|
||||
pub fn tcc_with_prod_requested_in(args []string, cwd string) bool {
|
||||
mut prod := false
|
||||
mut ccompiler := ''
|
||||
mut backend := 'c'
|
||||
mut i := 0
|
||||
for i < args.len {
|
||||
arg := args[i]
|
||||
if arg == '-prod' {
|
||||
prod = true
|
||||
} else if arg in ['-cc', '-b', '-backend'] && i + 1 < args.len {
|
||||
i++
|
||||
if arg == '-cc' { ccompiler = args[i] } else { backend = args[i] }
|
||||
} else if arg.starts_with('-cc=') {
|
||||
ccompiler = arg['-cc='.len..]
|
||||
} else if arg.starts_with('-b=') {
|
||||
backend = arg['-b='.len..]
|
||||
} else if arg.starts_with('-backend=') {
|
||||
backend = arg['-backend='.len..]
|
||||
} else if arg in ['-prof', '-profile'] {
|
||||
if prod_profile_consumes_value(args, i) { i++ }
|
||||
} else if arg == '-cf' || option_may_consume_value(arg) {
|
||||
i++
|
||||
}
|
||||
i++
|
||||
}
|
||||
return prod && backend == 'c' && ccompiler != '' && ccompiler_is_tinycc_in(ccompiler, cwd)
|
||||
}
|
||||
|
||||
// prod_profile_consumes_value follows the launcher's optional profiling output rule.
|
||||
fn prod_profile_consumes_value(args []string, index int) bool {
|
||||
next := args[index + 1] or { return false }
|
||||
if next == '-' { return true }
|
||||
if next.starts_with('-') || next in ['run', 'build', 'test', 'doc']
|
||||
|| next.ends_with('.v') || next.ends_with('.vv') || next.ends_with('.vsh') || os.is_dir(next) {
|
||||
return false
|
||||
}
|
||||
return args[index + 2..].any(!it.starts_with('-'))
|
||||
}
|
||||
|
||||
// ccompiler_is_tinycc reports whether `ccompiler` is TinyCC. It resolves the path and any
|
||||
// symlink, then checks the file name with tinycc_file_name. Any other executable is asked for
|
||||
// its version banner, so a copy of tcc under another name is still recognised. Mentions of
|
||||
// TinyCC in metadata such as an installed directory do not identify a compiler. A name that
|
||||
// does not resolve to an executable is judged by its name alone.
|
||||
// Local launchable files precede PATH. Windows also permits loadable PE files with other extensions.
|
||||
pub fn ccompiler_is_tinycc(ccompiler string) bool {
|
||||
return ccompiler_is_tinycc_in(ccompiler, os.getwd())
|
||||
}
|
||||
|
||||
fn ccompiler_is_tinycc_in(ccompiler string, cwd string) bool {
|
||||
work_folder := if cwd == '' { os.getwd() } else { cwd }
|
||||
path := ccompiler_path_in(ccompiler, work_folder)
|
||||
if path == '' {
|
||||
mut name := ccompiler
|
||||
$if windows {
|
||||
name = ccompiler_windows_expand_env_vars(name)
|
||||
}
|
||||
return tinycc_file_name(os.file_name(name))
|
||||
}
|
||||
resolved := os.real_path(path)
|
||||
if tinycc_file_name(os.file_name(resolved)) {
|
||||
return true
|
||||
}
|
||||
if !os.is_file(resolved) {
|
||||
return false
|
||||
}
|
||||
$if !windows {
|
||||
if !os.is_executable(resolved) { return false }
|
||||
}
|
||||
for flag in ['--version', '-v'] {
|
||||
version := cmdexec.run_in(resolved, [flag], work_folder).output
|
||||
if ccompiler_version_is_tinycc(version) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ccompiler_path_in resolves explicit paths and PATH entries in the requested directory.
|
||||
// Match cmdexec's local-file and explicit-path rules before searching PATH.
|
||||
fn ccompiler_path_in(ccompiler string, cwd string) string {
|
||||
mut program := ccompiler
|
||||
$if windows {
|
||||
program = ccompiler_windows_expand_env_vars(program)
|
||||
}
|
||||
path := if os.is_abs_path(program) { program } else { os.join_path(cwd, program) }
|
||||
if os.is_file(path) && os.is_executable(path) { return path }
|
||||
mut explicit_path := os.is_abs_path(program) || program.contains('/')
|
||||
$if windows {
|
||||
explicit_path = explicit_path || program.contains('\\')
|
||||
}
|
||||
if explicit_path {
|
||||
$if windows {
|
||||
return ccompiler_windows_executable_path(path)
|
||||
} $else {
|
||||
return path
|
||||
}
|
||||
}
|
||||
mut suffixes := ['']
|
||||
$if windows {
|
||||
suffixes = ['.com', '.exe', '.bat', '.cmd', '']
|
||||
if os.file_ext(program).to_lower_ascii() in suffixes && os.file_ext(program) != '' {
|
||||
suffixes = ['']
|
||||
}
|
||||
}
|
||||
for suffix in suffixes {
|
||||
for entry in os.getenv('PATH').split(os.path_delimiter) {
|
||||
directory := if os.is_abs_path(entry) { entry } else { os.join_path(cwd, entry) }
|
||||
candidate := os.join_path(directory, program + suffix)
|
||||
if os.is_file(candidate) && os.is_executable(candidate) { return candidate }
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// Explicit Windows paths have CreateProcessW's single implicit `.exe` suffix.
|
||||
// Nonstandard extensions can still contain a loadable PE image.
|
||||
fn ccompiler_windows_executable_path(candidate string) string {
|
||||
if os.is_file(candidate) && os.is_executable(candidate) { return candidate }
|
||||
if os.file_ext(candidate) != '' { return candidate }
|
||||
probed := candidate + '.exe'
|
||||
if os.is_file(probed) && os.is_executable(probed) { return probed }
|
||||
return candidate
|
||||
}
|
||||
|
||||
// Keep the same single-pass percent expansion as cmdexec's Windows launch path.
|
||||
fn ccompiler_windows_expand_env_vars(text string) string {
|
||||
if !text.contains('%') { return text }
|
||||
parts := text.split('%')
|
||||
mut out := strings.new_builder(text.len)
|
||||
for i in 0 .. parts.len {
|
||||
if i % 2 == 0 {
|
||||
out.write_string(parts[i])
|
||||
continue
|
||||
}
|
||||
if i == parts.len - 1 {
|
||||
out.write_string('%')
|
||||
out.write_string(parts[i])
|
||||
continue
|
||||
}
|
||||
name := parts[i]
|
||||
value := if name == '' { '' } else { os.getenv(name) }
|
||||
if value != '' {
|
||||
out.write_string(value)
|
||||
continue
|
||||
}
|
||||
out.write_string('%')
|
||||
out.write_string(name)
|
||||
out.write_string('%')
|
||||
}
|
||||
return out.str()
|
||||
}
|
||||
|
||||
// ccompiler_version_is_tinycc reports whether output contains a TinyCC version or branding
|
||||
// banner line. Paths and other metadata containing the compiler's name are ignored.
|
||||
pub fn ccompiler_version_is_tinycc(output string) bool {
|
||||
for line in output.to_lower_ascii().split_into_lines() {
|
||||
banner := line.trim_space()
|
||||
if banner in ['tcc', 'tinycc', 'tiny c compiler']
|
||||
|| banner.starts_with('tcc version ') || banner.starts_with('tinycc version ')
|
||||
|| banner.starts_with('tiny c compiler ') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// tinycc_file_name reports whether `file_name` is tcc or tinyc, with an optional `.exe` and an
|
||||
// optional target prefix such as `x86_64-w64-mingw32-tcc`. A name that only contains tcc, such
|
||||
// as `distcc` or `notcc`, is not TinyCC.
|
||||
pub fn tinycc_file_name(file_name string) bool {
|
||||
mut name := file_name.to_lower_ascii()
|
||||
if name.ends_with('.exe') {
|
||||
name = name[..name.len - '.exe'.len]
|
||||
}
|
||||
return name in ['tcc', 'tinyc', 'tinycc'] || name.ends_with('-tcc') || name.ends_with('-tinycc')
|
||||
}
|
||||
|
||||
// find_system_assembler returns a GCC or Clang compatible compiler driver found
|
||||
// on PATH, one that can assemble a preprocessed `.S` file, or none.
|
||||
pub fn find_system_assembler() ?string {
|
||||
|
||||
@@ -59,7 +59,7 @@ fn test_empty_array_literal_before_logical_and_in_conditions() {
|
||||
else { 100 }
|
||||
}
|
||||
assert hits == 3
|
||||
assert 1 in [1] && flag
|
||||
assert 1 in [1, 2] && flag
|
||||
assert 1 !in [] && flag
|
||||
}
|
||||
|
||||
@@ -98,8 +98,8 @@ fn test_comments_separate_logical_and_after_array_literals() {
|
||||
assert a == [] && // separator
|
||||
flag
|
||||
assert a == [] /* separator */
|
||||
&& flag
|
||||
&& flag
|
||||
fixed := [1]!
|
||||
assert fixed == [1]! /* separator */
|
||||
&& flag
|
||||
&& flag
|
||||
}
|
||||
@@ -7,5 +7,5 @@ fn test_calling_functions_with_map_initializations_containing_arrays() {
|
||||
'users': []string{}
|
||||
'groups': []string{}
|
||||
}, escape_unicode: true)
|
||||
assert result == '{"users":[],"groups":[]}'
|
||||
assert result == '{"groups":[],"users":[]}'
|
||||
}
|
||||
@@ -238,7 +238,7 @@ fn test_default_cache_keeps_the_closure_runtime_when_module_bodies_are_not_parse
|
||||
}
|
||||
}
|
||||
|
||||
fn test_tcc_retries_programs_that_cannot_use_cached_declarations() {
|
||||
fn test_tcc_uses_cached_declarations_for_anonymous_and_fixed_arrays() {
|
||||
$if !linux && !macos {
|
||||
return
|
||||
}
|
||||
@@ -257,10 +257,13 @@ fn test_tcc_retries_programs_that_cannot_use_cached_declarations() {
|
||||
os.setenv('V3CACHE', os.join_path(root, 'cache${i}'), true)
|
||||
for fixture in fixtures {
|
||||
os.write_file(main_file, os.read_file(os.join_path(@VEXEROOT, 'vlib/v/tests', fixture))!)!
|
||||
for _ in 0 .. 2 {
|
||||
for iteration in 0 .. 2 {
|
||||
output := build(root, flags, main_file, 'program')
|
||||
assert output.contains('the bundled TinyCC could not compile or link the cached program'), output
|
||||
assert !output.contains('the bundled TinyCC could not compile or link the cached program'), output
|
||||
assert !output.contains('implicit tcc could not be used'), output
|
||||
if iteration == 1 {
|
||||
assert_reused_modules(output)
|
||||
}
|
||||
_ := run_built(root, 'program')
|
||||
}
|
||||
}
|
||||
@@ -859,6 +862,69 @@ exec ${os.quoted_path(cc)} "$@"
|
||||
return saved
|
||||
}
|
||||
|
||||
fn test_warm_tuple_signatures_retain_nested_struct_payload_layouts() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
for flags in [['-cc', 'cc'], ['-cc', 'tcc']] {
|
||||
root := new_project('module_cache_tuple_payload_${flags[1]}')
|
||||
mut saved := pin_module_cache(os.join_path(root, 'cache'))
|
||||
saved << save_env('VMODULES')
|
||||
if flags[1] == 'cc' { saved << capture_cached_program_c(root) }
|
||||
os.setenv('VMODULES', os.join_path(root, 'modules'), true)
|
||||
defer {
|
||||
for env in saved { env.restore() }
|
||||
os.rmdir_all(root) or {}
|
||||
}
|
||||
os.mkdir_all(os.join_path(root, 'modules', 'cachetuple'))!
|
||||
os.write_file(os.join_path(root, 'modules', 'cachetuple', 'cachetuple.v'), 'module cachetuple
|
||||
pub struct Child {
|
||||
pub:
|
||||
value int
|
||||
}
|
||||
pub struct Box {
|
||||
pub:
|
||||
child Child
|
||||
}
|
||||
pub struct Unused { value int }
|
||||
pub fn marker() int { return 1 }
|
||||
pub fn optional_pair() ?(Box, string) { return Box{ child: Child{ value: 41 } }, "option" }
|
||||
pub fn result_pair() !(Box, string) { return Box{ child: Child{ value: 42 } }, "result" }
|
||||
')!
|
||||
main_file := os.join_path(root, 'main.v')
|
||||
os.write_file(main_file, 'import cachetuple\nfn main() { println(cachetuple.marker()) }\n')!
|
||||
build(root, flags, main_file, 'cold')
|
||||
assert run_built(root, 'cold') == '1'
|
||||
published := module_objects(root)
|
||||
program := 'import cachetuple
|
||||
fn main() {
|
||||
option_box, option_label := cachetuple.optional_pair() or { panic("none") }
|
||||
result_box, result_label := cachetuple.result_pair() or { panic(err) }
|
||||
println("\${option_box.child.value} \${option_label} \${result_box.child.value} \${result_label}")
|
||||
}
|
||||
'
|
||||
for i in 0 .. 2 {
|
||||
os.write_file(main_file, program + '\nfn added_${i}() {}\n')!
|
||||
mut warm_flags := flags.clone()
|
||||
if i == 1 { warm_flags << '-no-parallel' }
|
||||
warm := build(root, warm_flags, main_file, 'warm')
|
||||
assert_reused_modules(warm)
|
||||
assert module_objects(root) == published
|
||||
assert run_built(root, 'warm') == '41 option 42 result'
|
||||
if flags[1] == 'cc' {
|
||||
output := os.read_file(os.join_path(root, 'program.c'))!
|
||||
child_position := output.index('struct cachetuple__Child {') or { -1 }
|
||||
box_position := output.index('struct cachetuple__Box {') or { -1 }
|
||||
tuple_position := output.index('struct multi_return_cachetuple__Box_string {') or { -1 }
|
||||
assert child_position >= 0, output
|
||||
assert box_position > child_position, output
|
||||
assert tuple_position > box_position, output
|
||||
assert !output.contains('struct cachetuple__Unused {'), output
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_unused_cached_option_and_result_constants_do_not_pull_in_payload_declarations() {
|
||||
$if windows {
|
||||
return
|
||||
|
||||
@@ -25,7 +25,7 @@ fn test_recursive_struct_equality_compares_content_at_every_depth() {
|
||||
assert a != different
|
||||
assert !(a == different)
|
||||
assert [a] == [b]
|
||||
assert a in [b]
|
||||
assert a in [different, b]
|
||||
left_map := {
|
||||
'node': a
|
||||
}
|
||||
|
||||
@@ -4,6 +4,18 @@ import v.flat
|
||||
import v.token
|
||||
import v.types
|
||||
|
||||
fn test_anonymous_struct_placeholders_inside_fixed_arrays_are_unresolved() {
|
||||
for skip_generics in [false, true] {
|
||||
t := Transformer{ skip_generics: skip_generics }
|
||||
for typ in ['[2]struct', '[2][3]struct', '[][2]struct', '?[2]struct', 'map[string][2]struct'] {
|
||||
assert t.generic_arg_is_unresolved(typ), typ
|
||||
}
|
||||
for typ in ['[2]int', '[2][3]string', '[][2]int', '?[2]int'] {
|
||||
assert !t.generic_arg_is_unresolved(typ), typ
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_inferred_anonymous_struct(mut a flat.FlatAst, file_id int) (flat.NodeId, flat.NodeId) {
|
||||
pos := token.new_pos(file_id, 0)
|
||||
value_id := a.add_node(flat.Node{
|
||||
|
||||
@@ -1128,7 +1128,13 @@ fn (mut t Transformer) transform_array_literal_for_type(id flat.NodeId, node fla
|
||||
}
|
||||
|
||||
fn (mut t Transformer) transform_fixed_array_literal_for_type(_id flat.NodeId, node flat.Node, target_type string) ?flat.NodeId {
|
||||
fixed_type := t.normalize_type_alias(target_type)
|
||||
mut fixed_type := t.normalize_type_alias(target_type)
|
||||
if t.generic_arg_is_unresolved(fixed_type) {
|
||||
elem_type := t.array_literal_elem_type(node)
|
||||
if decl_type_is_usable(elem_type) && !t.generic_arg_is_unresolved(elem_type) {
|
||||
fixed_type = '[${fixed_array_len_text(fixed_type)}]${elem_type}'
|
||||
}
|
||||
}
|
||||
if !t.is_fixed_array_type(fixed_type) {
|
||||
return none
|
||||
}
|
||||
|
||||
+35
-2
@@ -6715,13 +6715,46 @@ fn auto_str_helper_name(aggregate string) string {
|
||||
}
|
||||
|
||||
fn (t &Transformer) auto_str_helper_owner_module(aggregate string) string {
|
||||
base_name := aggregate.all_before('[')
|
||||
if !isnil(t.tc) {
|
||||
if module_name := t.tc.struct_modules[aggregate] {
|
||||
return module_name
|
||||
}
|
||||
if module_name := t.tc.type_alias_modules[aggregate] {
|
||||
return module_name
|
||||
}
|
||||
// Bare interfaces belong to main or builtin, even when a library
|
||||
// specialization requests their recursive stringification helper.
|
||||
if !base_name.contains('.') {
|
||||
for node in t.a.nodes {
|
||||
if node.kind != .interface_decl || node.value != base_name {
|
||||
continue
|
||||
}
|
||||
if source := t.a.source_files[node.pos.id] {
|
||||
// Generated helper contexts can revisit a source file in a foreign
|
||||
// namespace. The parsed file's module remains its declaration owner.
|
||||
for file_node in t.a.nodes {
|
||||
if file_node.kind == .file && file_node.children_count > 0
|
||||
&& (file_node.value == source.name
|
||||
|| t.a.real_source_path(file_node.value) == t.a.real_source_path(source.name)) {
|
||||
module_name := t.file_module_name(file_node)
|
||||
if module_name in ['', 'main', 'builtin'] {
|
||||
return if module_name.len > 0 { module_name } else { 'main' }
|
||||
}
|
||||
}
|
||||
}
|
||||
module_name := t.tc.file_modules[source.name] or {
|
||||
t.tc.file_modules[t.a.real_source_path(source.name)] or { continue }
|
||||
}
|
||||
if module_name in ['', 'main', 'builtin'] {
|
||||
return module_name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !aggregate.starts_with('C.') && aggregate.contains('.') {
|
||||
return aggregate.all_before_last('.')
|
||||
if !base_name.starts_with('C.') && base_name.contains('.') {
|
||||
return base_name.all_before_last('.')
|
||||
}
|
||||
return if t.cur_module.len > 0 { t.cur_module } else { 'main' }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.token
|
||||
import v.types
|
||||
|
||||
fn literal_binding_test_node(mut a flat.FlatAst, kind flat.NodeKind, children []flat.NodeId) flat.NodeId {
|
||||
@@ -837,6 +838,46 @@ fn test_auto_str_helper_call_uses_type_owner_module() {
|
||||
assert a.nodes[helper_idx - 1].value == 'token'
|
||||
}
|
||||
|
||||
fn test_auto_str_helper_uses_interface_declaration_module() {
|
||||
for owner in ['main', 'builtin', 'view'] {
|
||||
mut a := flat.FlatAst.new()
|
||||
file := '${owner}.v'
|
||||
a.source_files[1] = token.File.unindexed(file, 1)
|
||||
name := if owner == 'view' { 'view.View' } else { 'View' }
|
||||
module_id := a.add_val(.module_decl, owner)
|
||||
decl_id := a.add_node(flat.Node{
|
||||
kind: .interface_decl
|
||||
value: 'View'
|
||||
pos: token.new_pos(1, 0)
|
||||
})
|
||||
file_children := a.children.len
|
||||
a.children << module_id
|
||||
a.children << decl_id
|
||||
a.add_node(flat.Node{
|
||||
kind: .file
|
||||
value: file
|
||||
children_start: i32(file_children)
|
||||
children_count: 2
|
||||
})
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
module_path := if owner == 'main' { a.real_source_path(file) } else { file }
|
||||
tc.file_modules[module_path] = 'datatypes'
|
||||
mut t := Transformer{
|
||||
a: &a
|
||||
tc: &tc
|
||||
cur_module: 'datatypes'
|
||||
cur_file: 'datatypes/linked_list.v'
|
||||
}
|
||||
value := t.make_ident('view')
|
||||
t.stringify_stack << 'Wrapper'
|
||||
for suffix in ['', '[int]', '[pkg.Item]'] {
|
||||
application := name + suffix
|
||||
t.request_auto_str_helper(value, application)
|
||||
assert t.auto_str_types[application].helper_module == owner, application
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_default_clone_helper_drops_owned_rvalue_after_saving_clone() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut t := Transformer{
|
||||
|
||||
@@ -15673,6 +15673,12 @@ fn (t &Transformer) generic_arg_is_unresolved_uncached(arg string) bool {
|
||||
if clean.starts_with('[]') {
|
||||
return t.generic_arg_is_unresolved(clean[2..])
|
||||
}
|
||||
if clean.starts_with('[') {
|
||||
bracket_end := generic_matching_bracket(clean, 0)
|
||||
if bracket_end < clean.len {
|
||||
return t.generic_arg_is_unresolved(clean[bracket_end + 1..])
|
||||
}
|
||||
}
|
||||
if clean.starts_with('map[') {
|
||||
bracket_end := generic_matching_bracket(clean, 3)
|
||||
if bracket_end < clean.len {
|
||||
|
||||
@@ -372,6 +372,11 @@ fn (mut tc TypeChecker) check_import_symbol_conflict(id flat.NodeId, name string
|
||||
}
|
||||
|
||||
fn (mut tc TypeChecker) check_import_symbol_conflict_at(id flat.NodeId, name string, pos token.Pos) {
|
||||
// A cached header combines imports from different source files. Its names
|
||||
// were already checked in their original file scopes before being cached.
|
||||
if is_module_cache_header(tc.cur_file) {
|
||||
return
|
||||
}
|
||||
if name == '' || name == '_' || !tc.has_active_import(name) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,6 +5,26 @@ import v.flat
|
||||
import v.parser
|
||||
import v.pref
|
||||
|
||||
fn test_cached_header_parameter_does_not_conflict_with_merged_imports() {
|
||||
root := os.join_path(os.vtmp_dir(), 'v3_module_cache_import_params_${os.getpid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
for file_name in ['sample.v', 'sample.vh'] {
|
||||
path := os.join_path(root, file_name)
|
||||
os.write_file(path, 'module sample\nimport math.bits\npub fn prime(bits int) int { return bits }\n')!
|
||||
mut p := parser.Parser.new(pref.new_preferences())
|
||||
a := p.parse_file(path)
|
||||
mut tc := TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.check_semantics()
|
||||
if file_name.ends_with('.vh') {
|
||||
assert tc.errors.len == 0, tc.errors.str()
|
||||
} else {
|
||||
assert tc.errors.any(it.msg == 'duplicate of an import symbol `bits`'), tc.errors.str()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_cached_module_header_uses_original_source_identity() {
|
||||
root := os.join_path(os.vtmp_dir(), 'v3_cached_module_identity_${os.getpid()}')
|
||||
source := os.join_path(root, 'app', 'html', 'html.v')
|
||||
|
||||
@@ -6,6 +6,7 @@ import strconv
|
||||
import strings
|
||||
import time
|
||||
import v.cmdexec
|
||||
import v.pref
|
||||
import v.tempname
|
||||
|
||||
const record_prefix = '.vup_refresh_'
|
||||
@@ -17,6 +18,12 @@ const object_record_format = 'vup-cache-refresh-3'
|
||||
const cwd_record_format = 'vup-cache-refresh-2'
|
||||
const legacy_record_format = 'vup-cache-refresh-1'
|
||||
|
||||
// request_uses_prod_tcc applies the production restriction using the recorded compiler
|
||||
// paths and working directory, including context-sensitive compiler wrappers.
|
||||
fn request_uses_prod_tcc(request Request) bool {
|
||||
return pref.tcc_with_prod_requested_in(request.args, request.cwd)
|
||||
}
|
||||
|
||||
// FlagGroup preserves the ordering and deduplication of a project's C directives.
|
||||
pub struct FlagGroup {
|
||||
pub:
|
||||
@@ -550,7 +557,10 @@ pub fn collect(vroot string, vexe string) ![]Request {
|
||||
// Live entries whose project source identity changed are skipped until rebuilt and recorded.
|
||||
// Legacy headers are marked migrated only after successful refreshes of their variants.
|
||||
// Disk-full failures stop remaining builds; child output is forwarded without buffering it all.
|
||||
pub fn refresh(vexe string, requests []Request) ! {
|
||||
// TinyCC production builds are never refreshed: the compiler refuses `-cc tcc` with `-prod`,
|
||||
// and records made before that rule still exist.
|
||||
pub fn refresh(vexe string, all_requests []Request) ! {
|
||||
requests := all_requests.filter(!request_uses_prod_tcc(it))
|
||||
if requests.len == 0 { return }
|
||||
dir := os.join_path(os.vtmp_dir(), 'vup_cache_refresh_${tempname.unique_token()}')
|
||||
os.mkdir_all(dir)!
|
||||
|
||||
@@ -701,6 +701,8 @@ fn test_refresh_rebuilds_changed_vlib_dependencies_for_each_compiler_and_mode()
|
||||
}
|
||||
for mode in modes {
|
||||
for prod in [false, true] {
|
||||
// TinyCC is not a -prod compiler, so there is no production TinyCC build to check.
|
||||
if compiler == 'tcc' && prod { continue }
|
||||
mut flags := ['-cc', compiler, '-gc', 'none', '-path', 'modules/../modules|@vlib',
|
||||
'-file-list', '../shared support.v,../implementers']
|
||||
if prod { flags << '-prod' }
|
||||
@@ -1015,3 +1017,52 @@ fn test_refresh_replays_and_clears_test_assert_display_modes() ! {
|
||||
assert os.read_file(log)!.split_into_lines() == ['1', 'unset']
|
||||
assert os.getenv('VTEST_SHOW_ASSERTS') == 'ambient'
|
||||
}
|
||||
|
||||
fn test_refresh_skips_production_tinycc_alias_before_starting_builds() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'cache_refresh_prod_tcc_${os.getpid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
alias := os.join_path(root, 'cc')
|
||||
os.write_file(alias, '#!/bin/sh\nprintf "tcc version 0.9.27\\n"\n')!
|
||||
os.chmod(alias, 0o700)!
|
||||
request := Request{ args: ['-prod', '-cc', './cc'], cwd: root, module: 'builtin' }
|
||||
assert request_uses_prod_tcc(request)
|
||||
equals_request := Request{ ...request, args: ['-prod', '-cc=./cc'] }
|
||||
assert request_uses_prod_tcc(equals_request)
|
||||
refresh(os.join_path(root, 'missing-v'), [request])!
|
||||
project := os.join_path(root, 'tinycc-project')
|
||||
os.mkdir_all(project)!
|
||||
os.mkdir_all(os.join_path(root, 'vlib', 'builtin'))!
|
||||
wrapper := os.join_path(root, 'context-wrapper')
|
||||
os.write_file(wrapper, '#!/bin/sh\ncase "$PWD" in\n*/tinycc-project) echo "tcc version 0.9.27" ;;\n*) echo "clang version 19.1.7" ;;\nesac\n')!
|
||||
os.chmod(wrapper, 0o700)!
|
||||
marker := os.join_path(root, 'refresh-started')
|
||||
fake_v := os.join_path(root, 'fake-v')
|
||||
os.write_file(fake_v, '#!/bin/sh\necho started > ' + os.quoted_path(marker) + '\nexit 91\n')!
|
||||
os.chmod(fake_v, 0o700)!
|
||||
before := os.getwd()
|
||||
for args in [['-prod', '-cc', wrapper], ['-prod', '-cc', '../context-wrapper'],
|
||||
['-prod', '-cc=../context-wrapper']] {
|
||||
context_request := Request{ ...request, args: args, cwd: project }
|
||||
assert request_uses_prod_tcc(context_request)
|
||||
refresh(fake_v, [context_request])!
|
||||
assert !os.exists(marker)
|
||||
}
|
||||
assert !request_uses_prod_tcc(Request{ ...request, args: ['-prod', '-cc', wrapper] })
|
||||
assert os.getwd() == before
|
||||
bare := 'cache_context_cc_${os.getpid()}'
|
||||
os.cp(wrapper, os.join_path(project, bare))!
|
||||
os.chmod(os.join_path(project, bare), 0o700)!
|
||||
saved_path := os.getenv('PATH')
|
||||
defer { os.setenv('PATH', saved_path, true) }
|
||||
os.setenv('PATH', '.' + os.path_delimiter + saved_path, true)
|
||||
context_request := Request{ ...request, args: ['-prod', '-cc', bare], cwd: project }
|
||||
assert request_uses_prod_tcc(context_request)
|
||||
refresh(fake_v, [context_request])!
|
||||
assert !os.exists(marker)
|
||||
assert os.getenv('PATH') == '.' + os.path_delimiter + saved_path
|
||||
assert os.getwd() == before
|
||||
}
|
||||
Reference in new issue
Block a user