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