v / cmd / tools / oldv.v
199 lines · 187 sloc · 7.61 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1import os
2import flag
3import scripting
4import vgit
5
6const tool_version = '0.0.4'
7const tool_description = ' Checkout an old version of V and compile it as it was on a specific commit.
8| This tool is useful when you want to discover when something broke.
9| It is also useful when you just want to experiment with an older, historic V.
10|
11| The VCOMMIT argument can be a git commit object like HEAD or master and so on.
12| When oldv is used with git bisect, you probably want to use HEAD. For example:
13| git bisect start
14| git bisect bad
15| git checkout known_good_commit
16| git bisect good
17| ## git will automatically checkout a middle commit between the bad and the good
18| cmd/tools/oldv --bisect --command="run commands in oldv folder, to verify if the commit is good or bad
19| ## Examine the results, and either do: ...
20| git bisect good
21| ## ... or do:
22| git bisect bad
23| ## Repeat the above steps, each time running oldv with the same command, then mark the result as good
24| ## or bad until you find the commit where the problem first occurred.
25| ## When you finish, do not forget to do:
26| git bisect reset'.strip_margin()
27
28struct Context {
29mut:
30 vgo vgit.VGitOptions
31 vgcontext vgit.VGitContext
32 commit_v string = 'master' // the commit from which you want to produce a working v compiler (this may be a commit-ish too)
33 commit_v_hash string // this will be filled from the commit-ish commit_v using rev-list. It IS a commit hash.
34 path_v string // the full path to the v folder inside workdir.
35 path_vc string // the full path to the vc folder inside workdir.
36 cmd_to_run string // the command that you want to run *in* the oldv repo
37 cleanup bool // should the tool run a cleanup first
38 use_cache bool // use local cached copies for --vrepo and --vcrepo in
39 fresh_tcc bool // do use `make fresh_tcc`
40 is_bisect bool // bisect mode; usage: `cmd/tools/oldv -b -c './v run bug.v'`
41 show_vccommit bool // show the V and VC commits, corresponding to the V commit-ish, that can be used to build V
42 cc string = 'cc' // the C compiler to use for bootstrapping.
43 cc_options string // additional options to pass to the C compiler while bootstrapping.
44}
45
46fn (mut c Context) compile_oldv_if_needed() {
47 c.vgcontext = vgit.VGitContext{
48 workdir: c.vgo.workdir
49 v_repo_url: c.vgo.v_repo_url
50 vc_repo_url: c.vgo.vc_repo_url
51 cc: c.cc
52 cc_options: c.cc_options
53 commit_v: c.commit_v
54 path_v: c.path_v
55 path_vc: c.path_vc
56 make_fresh_tcc: c.fresh_tcc
57 show_vccommit: c.show_vccommit
58 }
59 c.vgcontext.compile_oldv_if_needed()
60 c.commit_v_hash = c.vgcontext.commit_v__hash
61 if !os.exists(c.vgcontext.vexepath) && c.cmd_to_run.len > 0 {
62 // Note: 125 is a special code, that git bisect understands as 'skip this commit'.
63 // it is used to inform git bisect that the current commit leads to a build failure.
64 exit(125)
65 }
66}
67
68const cache_oldv_folder = os.join_path(os.cache_dir(), 'oldv')
69
70const cache_oldv_folder_v = os.join_path(cache_oldv_folder, 'v')
71
72const cache_oldv_folder_vc = os.join_path(cache_oldv_folder, 'vc')
73
74fn sync_cache() {
75 scripting.verbose_trace(@FN, 'start')
76 if !os.exists(cache_oldv_folder) {
77 scripting.verbose_trace(@FN, 'creating ${cache_oldv_folder}')
78 scripting.mkdir_all(cache_oldv_folder) or {
79 scripting.verbose_trace(@FN, '## failed.')
80 exit(1)
81 }
82 }
83 scripting.chdir(cache_oldv_folder)
84 for reponame in ['v', 'vc'] {
85 repofolder := os.join_path(cache_oldv_folder, reponame)
86 repo_url := 'https://github.com/vlang/${reponame}'
87 scripting.verbose_trace(@FN, 'syncing ${repo_url} to ${repofolder}')
88 vgit.clone_or_pull(repo_url, repofolder)
89 }
90 scripting.verbose_trace(@FN, 'done')
91}
92
93fn main() {
94 if os.user_os() == 'windows' {
95 scripting.used_tools_must_exist(['git', 'wc', 'make', 'robocopy'])
96 } else {
97 scripting.used_tools_must_exist(['git', 'wc', 'make', 'rsync', 'cc'])
98 }
99
100 // Resetting VEXE here allows for `v run cmd/tools/oldv.v'.
101 // the parent V would have set VEXE, which later will
102 // affect the V's run from the tool itself.
103 os.setenv('VEXE', '', true)
104
105 mut context := Context{}
106 context.vgo.workdir = cache_oldv_folder
107 mut fp := flag.new_flag_parser(os.args)
108 fp.application(os.file_name(os.executable()))
109 fp.version(tool_version)
110 fp.description(tool_description)
111 fp.arguments_description('VCOMMIT')
112 fp.skip_executable()
113 context.use_cache = fp.bool('cache', `u`, true,
114 'Use a cache of local repositories for --vrepo and --vcrepo in \$HOME/.cache/oldv/')
115 if context.use_cache {
116 context.vgo.v_repo_url = cache_oldv_folder_v
117 context.vgo.vc_repo_url = cache_oldv_folder_vc
118 } else {
119 context.vgo.v_repo_url = 'https://github.com/vlang/v'
120 context.vgo.vc_repo_url = 'https://github.com/vlang/vc'
121 }
122 context.cc = fp.string('cc', 0, 'cc',
123 'Use this C compiler for bootstrapping v.c (defaults to `cc`).')
124 context.cc_options = fp.string('ccoptions', 0, '',
125 'Use these C compiler options for bootstrapping v.c (defaults to ``).')
126 env_cc_options := os.getenv('OLDV_CCOPTIONS')
127 if env_cc_options != '' {
128 context.cc_options = env_cc_options
129 }
130 context.cleanup = fp.bool('clean', 0, false, 'Clean before running (slower).')
131 context.fresh_tcc = fp.bool('fresh_tcc', 0, true,
132 'Do `make fresh_tcc` when preparing a V compiler.')
133 context.cmd_to_run = fp.string('command', `c`, '', 'Command to run in the old V repo.\n')
134 context.show_vccommit = fp.bool('show_VC_commit', 0, false,
135 'Show the VC commit, that can be used to compile the given V commit, and exit.\n')
136 context.is_bisect = fp.bool('bisect', `b`, false,
137 'Bisect mode. Use the current commit in the repo where oldv is.')
138
139 should_sync := fp.bool('cache-sync', `s`, false, 'Update the local cache')
140 if !should_sync && !context.is_bisect {
141 fp.limit_free_args(1, 1)!
142 }
143
144 ////
145 commits := vgit.add_common_tool_options(mut context.vgo, mut fp)
146 if should_sync {
147 sync_cache()
148 println('Cache synced, cache folder: ${cache_oldv_folder} .')
149 exit(0)
150 }
151 if context.use_cache {
152 if !os.is_dir(cache_oldv_folder_v) || !os.is_dir(cache_oldv_folder_vc) {
153 sync_cache()
154 }
155 }
156 if commits.len > 0 {
157 context.commit_v = commits[0]
158 if context.is_bisect {
159 eprintln('In bisect mode, you should not pass any commits, since oldv will use the current one.')
160 exit(2)
161 }
162 } else {
163 context.commit_v = scripting.run('git rev-list -n1 HEAD')
164 }
165 if !context.show_vccommit {
166 scripting.cprintln('################# context.commit_v: ${context.commit_v} #####################')
167 }
168 context.path_v = vgit.normalized_workpath_for_commit(context.vgo.workdir, context.commit_v)
169 context.path_vc = vgit.normalized_workpath_for_commit(context.vgo.workdir, 'vc')
170 if !os.is_dir(context.vgo.workdir) {
171 eprintln('Work folder: ${context.vgo.workdir} , does not exist.')
172 exit(2)
173 }
174 ecc := os.getenv('CC')
175 if ecc != '' {
176 context.cc = ecc
177 }
178 if context.cleanup {
179 scripting.rmrf(context.path_v)
180 scripting.rmrf(context.path_vc)
181 }
182 context.compile_oldv_if_needed()
183 scripting.chdir(context.path_v)
184 shorter_hash := context.commit_v_hash[0..10]
185 scripting.cprintln('# v commit hash: ${shorter_hash} | folder: ${context.path_v}')
186 if context.cmd_to_run.len > 0 {
187 scripting.cprintln_strong('# command: ${context.cmd_to_run:-34s}')
188 cmdres := os.execute_or_exit(context.cmd_to_run)
189 if cmdres.exit_code != 0 {
190 scripting.cprintln_strong('# exit code: ${cmdres.exit_code:-4d}')
191 }
192 scripting.cprint_strong('# result: ')
193 print(cmdres.output)
194 if !cmdres.output.ends_with('\n') {
195 println('')
196 }
197 exit(cmdres.exit_code)
198 }
199}
200