| 1 | import os |
| 2 | import flag |
| 3 | import scripting |
| 4 | import vgit |
| 5 | |
| 6 | const tool_version = '0.0.4' |
| 7 | const 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 | |
| 28 | struct Context { |
| 29 | mut: |
| 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 | |
| 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 | 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 | |
| 68 | const cache_oldv_folder = os.join_path(os.cache_dir(), 'oldv') |
| 69 | |
| 70 | const cache_oldv_folder_v = os.join_path(cache_oldv_folder, 'v') |
| 71 | |
| 72 | const cache_oldv_folder_vc = os.join_path(cache_oldv_folder, 'vc') |
| 73 | |
| 74 | fn 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 | |
| 93 | fn 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 | |