Skip to content

Commit 6b892c0

Browse files
authored
Merge pull request #364 from thinkgos/auto-detect-go-mod-file
feat: impl auto detect go.mod file or go.work file go version
2 parents 22d637a + dc44e08 commit 6b892c0

File tree

3 files changed

+117
-21
lines changed

3 files changed

+117
-21
lines changed

src/command/install.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ impl Run for Install {
6464
registry.install_go(&version)?;
6565
version
6666
}
67-
Toolchain::Version(ver_req) => {
67+
Toolchain::Version(version_req) => {
6868
let version = if self.use_raw_version {
69-
ver_req
69+
version_req
7070
} else {
71-
registry_index.match_version_req( &ver_req).inspect_err(|_| {
71+
registry_index.match_version_req( &version_req).inspect_err(|_| {
7272
log::warn!("'semver' match failure, If you want to use version like '1.19beta1' or '1.25rc2', try add option '--use-raw-version'");
7373
})?
7474
};

src/command/shell.rs

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use std::os::unix::process::CommandExt;
33

44
use std::{
5-
env,
5+
env, fs,
66
path::Path,
77
process::{Command, Stdio},
88
};
@@ -12,8 +12,13 @@ use clap::Args;
1212
use dialoguer::{Select, theme::ColorfulTheme};
1313

1414
use crate::{
15-
command::utils::InstallOptions, consts::GOUP_GO_VERSION, dir::Dir, registry::Registry,
16-
shell::ShellType, toolchain, version::Version,
15+
command::utils::InstallOptions,
16+
consts::GOUP_GO_VERSION,
17+
dir::Dir,
18+
registry::{Registry, RegistryIndex},
19+
shell::ShellType,
20+
toolchain,
21+
version::Version,
1722
};
1823

1924
use super::Run;
@@ -32,7 +37,33 @@ pub struct Shell {
3237

3338
impl Run for Shell {
3439
fn run(&self) -> Result<(), anyhow::Error> {
35-
let go_version = self.get_target_version()?;
40+
let local_versions = Version::list_go_version()?;
41+
let go_version = self.get_target_version(&local_versions)?;
42+
43+
let mut current_shell_version = None;
44+
let mut current_default_version = None;
45+
for v in local_versions.iter() {
46+
if v.session {
47+
current_shell_version = Some(&v.version);
48+
}
49+
if v.default {
50+
current_default_version = Some(&v.version);
51+
}
52+
}
53+
54+
if current_shell_version == Some(&go_version)
55+
|| current_shell_version.is_none() && current_default_version == Some(&go_version)
56+
{
57+
// 如果当前会话和目标一样, 则不切换
58+
// 不在会话当中, 如果默认和目标一样, 则不切换
59+
log::info!(
60+
"Current environment already uses Go {go_version}, skip enter new shell session.",
61+
);
62+
63+
return Ok(());
64+
}
65+
66+
let go_version = toolchain::normalize(&go_version);
3667
let goup_home = Dir::goup_home()?;
3768
if !goup_home.is_dot_unpacked_success_file_exists(&go_version) {
3869
return Err(anyhow!(
@@ -72,7 +103,7 @@ impl Run for Shell {
72103
.collect::<Vec<_>>()
73104
.join(env_separator);
74105

75-
log::info!("Enter new shell session with go version: {}", go_version,);
106+
log::info!("Enter new shell session with Go {go_version}");
76107
let mut command = Command::new(shell.to_string());
77108
let command = command
78109
.stdin(Stdio::inherit())
@@ -98,39 +129,104 @@ impl Run for Shell {
98129
}
99130

100131
impl Shell {
101-
fn get_target_version(&self) -> Result<String, anyhow::Error> {
102-
let versions = Version::list_go_version()?;
132+
fn get_target_version(&self, local_versions: &[Version]) -> Result<String, anyhow::Error> {
103133
let target_version = if let Some(version) = &self.version {
104-
if !versions.iter().any(|v| v.version == *version) {
134+
if !local_versions.iter().any(|v| v.version == *version) {
105135
let registry = Registry::new(
106136
&self.install_options.registry,
107137
self.install_options.skip_verify,
108138
self.install_options.enable_check_archive_size,
109139
);
110140
registry.install_go(&toolchain::normalize(version))?
111141
}
112-
version
142+
version.to_owned()
143+
} else if let Some(ver) = self.get_mod_file_version(local_versions) {
144+
ver
113145
} else {
114-
if versions.is_empty() {
146+
if local_versions.is_empty() {
115147
return Err(anyhow!(
116148
"Not any go is installed, Install it with `goup install`."
117149
));
118150
}
119151
let mut items = Vec::new();
120-
let mut pos = 0;
121-
for (i, v) in versions.iter().enumerate() {
152+
153+
let mut session_pos = None;
154+
let mut default_pos = None;
155+
for (i, v) in local_versions.iter().enumerate() {
122156
items.push(&v.version);
157+
if v.session {
158+
session_pos = Some(i);
159+
}
123160
if v.default {
124-
pos = i;
161+
default_pos = Some(i);
125162
}
126163
}
164+
let pos = session_pos.unwrap_or(default_pos.unwrap_or(0));
127165
let selection = Select::with_theme(&ColorfulTheme::default())
128166
.with_prompt("Select a version")
129167
.items(&items)
130168
.default(pos)
131169
.interact()?;
132-
items[selection]
170+
items[selection].to_owned()
171+
};
172+
173+
Ok(target_version)
174+
}
175+
176+
fn get_mod_file_version(&self, local_versions: &[Version]) -> Option<String> {
177+
let current_dir = env::current_dir().ok()?;
178+
let mod_go_version = ["go.work", "go.mod"]
179+
.into_iter()
180+
.find_map(|filename| Self::parse_go_mod_or_work_file(current_dir.join(filename)))?;
181+
182+
let version_req = match mod_go_version.chars().filter(|&v| v == '.').count() {
183+
0 | 1 => format!("~{mod_go_version}"),
184+
_ => format!("={mod_go_version}"),
133185
};
134-
Ok(toolchain::normalize(target_version))
186+
let version = RegistryIndex::new(&self.install_options.registry_index)
187+
.match_version_req(&version_req)
188+
.ok()?;
189+
if !local_versions.iter().any(|v| v.version == version) {
190+
let registry = Registry::new(
191+
&self.install_options.registry,
192+
self.install_options.skip_verify,
193+
self.install_options.enable_check_archive_size,
194+
);
195+
registry.install_go(&toolchain::normalize(&version)).ok();
196+
}
197+
Some(version)
198+
}
199+
fn parse_go_mod_or_work_file(path: impl AsRef<Path>) -> Option<String> {
200+
if !path.as_ref().exists() {
201+
return None;
202+
}
203+
let mut target = None;
204+
let content = fs::read_to_string(path).ok()?;
205+
for line in content.lines() {
206+
let line = line.trim();
207+
if line.starts_with("//") || line.is_empty() {
208+
continue;
209+
}
210+
// https://go.dev/ref/mod#go-mod-file-go
211+
// go directive
212+
if let Some(rest) = line.strip_prefix("go ") {
213+
// go 1.22
214+
let v = rest.trim().to_string();
215+
target = Some(v);
216+
continue;
217+
}
218+
// https://go.dev/ref/mod#go-mod-file-toolchain
219+
// toolchain directive
220+
if let Some(rest) = line.strip_prefix("toolchain ") {
221+
// toolchain go1.22.3
222+
let rest = rest.trim().trim_start_matches("go").to_string();
223+
if rest.is_empty() {
224+
continue;
225+
}
226+
target = Some(rest);
227+
continue;
228+
}
229+
}
230+
target
135231
}
136232
}

src/registry.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,9 @@ impl RegistryIndex {
190190
.ok_or_else(|| anyhow!("Getting latest Go version failed"))
191191
.map(|v| v.to_owned())
192192
}
193-
pub fn match_version_req(&self, ver_pattern: &str) -> Result<String, anyhow::Error> {
194-
log::debug!("version request pattern: {ver_pattern}");
195-
let ver_req = VersionReq::parse(ver_pattern)?;
193+
pub fn match_version_req(&self, version_req: &str) -> Result<String, anyhow::Error> {
194+
log::debug!("version request: {version_req}");
195+
let ver_req = VersionReq::parse(version_req)?;
196196
let index_go = LocalGoIndex::read();
197197
let search_type = index_go.map_or(Ok(SearchType::Upstream), |v| v.try_search(&ver_req))?;
198198
if let SearchType::Local(ver) = search_type {

0 commit comments

Comments
 (0)