11from __future__ import annotations
22
33import contextlib
4+ import functools
5+ import json
46import os .path
7+ import platform
8+ import shutil
59import sys
10+ import tarfile
11+ import tempfile
12+ import urllib .error
13+ import urllib .request
14+ import zipfile
15+ from typing import ContextManager
616from typing import Generator
17+ from typing import IO
18+ from typing import Protocol
719from typing import Sequence
820
921import pre_commit .constants as C
1729from pre_commit .util import rmtree
1830
1931ENVIRONMENT_DIR = 'golangenv'
20- get_default_version = helpers .basic_get_default_version
2132health_check = helpers .basic_health_check
2233
34+ _ARCH_ALIASES = {
35+ 'x86_64' : 'amd64' ,
36+ 'i386' : '386' ,
37+ 'aarch64' : 'arm64' ,
38+ 'armv8' : 'arm64' ,
39+ 'armv7l' : 'armv6l' ,
40+ }
41+ _ARCH = platform .machine ().lower ()
42+ _ARCH = _ARCH_ALIASES .get (_ARCH , _ARCH )
43+
44+
45+ class ExtractAll (Protocol ):
46+ def extractall (self , path : str ) -> None : ...
47+
48+
49+ if sys .platform == 'win32' : # pragma: win32 cover
50+ _EXT = 'zip'
51+
52+ def _open_archive (bio : IO [bytes ]) -> ContextManager [ExtractAll ]:
53+ return zipfile .ZipFile (bio )
54+ else : # pragma: win32 no cover
55+ _EXT = 'tar.gz'
56+
57+ def _open_archive (bio : IO [bytes ]) -> ContextManager [ExtractAll ]:
58+ return tarfile .open (fileobj = bio )
59+
60+
61+ @functools .lru_cache (maxsize = 1 )
62+ def get_default_version () -> str :
63+ if helpers .exe_exists ('go' ):
64+ return 'system'
65+ else :
66+ return C .DEFAULT
67+
68+
69+ def get_env_patch (venv : str , version : str ) -> PatchesT :
70+ if version == 'system' :
71+ return (
72+ ('PATH' , (os .path .join (venv , 'bin' ), os .pathsep , Var ('PATH' ))),
73+ )
2374
24- def get_env_patch (venv : str ) -> PatchesT :
2575 return (
26- ('PATH' , (os .path .join (venv , 'bin' ), os .pathsep , Var ('PATH' ))),
76+ ('GOROOT' , os .path .join (venv , '.go' )),
77+ (
78+ 'PATH' , (
79+ os .path .join (venv , 'bin' ), os .pathsep ,
80+ os .path .join (venv , '.go' , 'bin' ), os .pathsep , Var ('PATH' ),
81+ ),
82+ ),
2783 )
2884
2985
86+ @functools .lru_cache
87+ def _infer_go_version (version : str ) -> str :
88+ if version != C .DEFAULT :
89+ return version
90+ resp = urllib .request .urlopen ('https://go.dev/dl/?mode=json' )
91+ # TODO: 3.9+ .removeprefix('go')
92+ return json .load (resp )[0 ]['version' ][2 :]
93+
94+
95+ def _get_url (version : str ) -> str :
96+ os_name = platform .system ().lower ()
97+ version = _infer_go_version (version )
98+ return f'https://dl.google.com/go/go{ version } .{ os_name } -{ _ARCH } .{ _EXT } '
99+
100+
101+ def _install_go (version : str , dest : str ) -> None :
102+ try :
103+ resp = urllib .request .urlopen (_get_url (version ))
104+ except urllib .error .HTTPError as e : # pragma: no cover
105+ if e .code == 404 :
106+ raise ValueError (
107+ f'Could not find a version matching your system requirements '
108+ f'(os={ platform .system ().lower ()} ; arch={ _ARCH } )' ,
109+ ) from e
110+ else :
111+ raise
112+ else :
113+ with tempfile .TemporaryFile () as f :
114+ shutil .copyfileobj (resp , f )
115+ f .seek (0 )
116+
117+ with _open_archive (f ) as archive :
118+ archive .extractall (dest )
119+ shutil .move (os .path .join (dest , 'go' ), os .path .join (dest , '.go' ))
120+
121+
30122@contextlib .contextmanager
31- def in_env (prefix : Prefix ) -> Generator [None , None , None ]:
32- envdir = helpers .environment_dir (prefix , ENVIRONMENT_DIR , C . DEFAULT )
33- with envcontext (get_env_patch (envdir )):
123+ def in_env (prefix : Prefix , version : str ) -> Generator [None , None , None ]:
124+ envdir = helpers .environment_dir (prefix , ENVIRONMENT_DIR , version )
125+ with envcontext (get_env_patch (envdir , version )):
34126 yield
35127
36128
@@ -39,15 +131,23 @@ def install_environment(
39131 version : str ,
40132 additional_dependencies : Sequence [str ],
41133) -> None :
42- helpers .assert_version_default ('golang' , version )
43134 env_dir = helpers .environment_dir (prefix , ENVIRONMENT_DIR , version )
44135
136+ if version != 'system' :
137+ _install_go (version , env_dir )
138+
45139 if sys .platform == 'cygwin' : # pragma: no cover
46140 gopath = cmd_output ('cygpath' , '-w' , env_dir )[1 ].strip ()
47141 else :
48142 gopath = env_dir
143+
49144 env = dict (os .environ , GOPATH = gopath )
50145 env .pop ('GOBIN' , None )
146+ if version != 'system' :
147+ env ['GOROOT' ] = os .path .join (env_dir , '.go' )
148+ env ['PATH' ] = os .pathsep .join ((
149+ os .path .join (env_dir , '.go' , 'bin' ), os .environ ['PATH' ],
150+ ))
51151
52152 helpers .run_setup_cmd (prefix , ('go' , 'install' , './...' ), env = env )
53153 for dependency in additional_dependencies :
@@ -64,5 +164,5 @@ def run_hook(
64164 file_args : Sequence [str ],
65165 color : bool ,
66166) -> tuple [int , bytes ]:
67- with in_env (hook .prefix ):
167+ with in_env (hook .prefix , hook . language_version ):
68168 return helpers .run_xargs (hook , hook .cmd , file_args , color = color )
0 commit comments