2020-03-16 13:06:32 +03:00
import fileinput
2023-03-21 05:43:06 +03:00
import json
import re
import shutil
2020-11-29 11:31:55 +03:00
import subprocess
2023-03-26 05:16:09 +03:00
import sys
2021-01-09 13:09:16 +03:00
import tempfile
2023-03-21 05:43:06 +03:00
import tomllib
from concurrent . futures import ThreadPoolExecutor
2022-11-28 18:43:41 +03:00
from os import path
2023-03-21 05:43:06 +03:00
from pathlib import Path
2020-03-16 13:06:32 +03:00
from . errors import UpdateError
2023-04-03 15:59:22 +03:00
from . eval import CargoLockInSource , CargoLockInStore , Package , eval_attr
2022-11-15 01:13:11 +03:00
from . git import old_version_from_git
2020-03-17 16:22:33 +03:00
from . options import Options
2020-04-21 12:06:55 +03:00
from . utils import info , run
from . version import fetch_latest_version
2022-11-24 20:33:58 +03:00
from . version . gitlab import GITLAB_API
2022-11-21 23:07:18 +03:00
from . version . version import Version , VersionPreference
2020-03-16 13:06:32 +03:00
2020-11-28 00:10:38 +03:00
def replace_version ( package : Package ) - > bool :
2022-01-15 15:44:02 +03:00
assert package . new_version is not None
2020-03-24 16:25:17 +03:00
old_version = package . old_version
2022-01-15 15:44:02 +03:00
new_version = package . new_version . number
2020-03-24 16:25:17 +03:00
if new_version . startswith ( " v " ) :
new_version = new_version [ 1 : ]
2020-03-16 13:06:32 +03:00
2023-04-15 05:59:01 +03:00
changed = old_version != new_version or (
package . new_version . rev is not None and package . new_version . rev != package . rev
)
if changed :
2020-03-24 16:25:17 +03:00
info ( f " Update { old_version } -> { new_version } in { package . filename } " )
with fileinput . FileInput ( package . filename , inplace = True ) as f :
2020-03-16 13:06:32 +03:00
for line in f :
2022-01-15 15:44:02 +03:00
if package . new_version . rev :
line = line . replace ( package . rev , package . new_version . rev )
2023-03-21 05:42:41 +03:00
print ( line . replace ( f ' " { old_version } " ' , f ' " { new_version } " ' ) , end = " " )
2020-03-17 15:36:50 +03:00
else :
2020-03-24 16:25:17 +03:00
info ( f " Not updating version, already { old_version } " )
2020-03-16 13:06:32 +03:00
2023-04-15 05:59:01 +03:00
return changed
2020-11-28 00:10:38 +03:00
2020-03-16 13:06:32 +03:00
2020-11-29 11:31:55 +03:00
def to_sri ( hashstr : str ) - > str :
if " - " in hashstr :
return hashstr
2022-11-21 00:09:48 +03:00
length = len ( hashstr )
if length == 32 :
2020-11-29 11:31:55 +03:00
prefix = " md5: "
2022-11-21 00:09:48 +03:00
elif length == 40 :
2020-11-29 11:31:55 +03:00
# could be also base32 == 32, but we ignore this case and hope no one is using it
prefix = " sha1: "
2022-11-21 00:09:48 +03:00
elif length == 64 or length == 52 :
2020-11-29 11:31:55 +03:00
prefix = " sha256: "
2022-11-21 00:09:48 +03:00
elif length == 103 or length == 128 :
2020-11-29 11:31:55 +03:00
prefix = " sha512: "
else :
return hashstr
cmd = [
" nix " ,
2022-05-19 10:52:15 +03:00
" --extra-experimental-features " ,
2020-11-29 11:31:55 +03:00
" nix-command " ,
2021-01-09 09:51:27 +03:00
" hash " ,
2020-11-29 11:31:55 +03:00
" to-sri " ,
f " { prefix } { hashstr } " ,
]
2022-11-17 20:49:14 +03:00
proc = run ( cmd )
2020-11-29 11:31:55 +03:00
return proc . stdout . rstrip ( " \n " )
2020-03-16 13:06:32 +03:00
def replace_hash ( filename : str , current : str , target : str ) - > None :
2020-11-29 11:31:55 +03:00
normalized_hash = to_sri ( target )
if to_sri ( current ) != normalized_hash :
2020-03-16 13:06:32 +03:00
with fileinput . FileInput ( filename , inplace = True ) as f :
for line in f :
2020-12-22 22:09:58 +03:00
line = line . replace ( current , normalized_hash )
2020-03-16 13:06:32 +03:00
print ( line , end = " " )
2023-03-21 05:43:06 +03:00
def get_package ( opts : Options ) - > str :
return (
2023-08-23 18:10:20 +03:00
f " (let flake = builtins.getFlake { opts . escaped_import_path } ; in flake.packages.$ {{ builtins.currentSystem }} . { opts . escaped_attribute } or flake. { opts . escaped_attribute } ) "
2023-01-05 03:55:31 +03:00
if opts . flake
2023-08-23 18:10:20 +03:00
else f " (import { opts . escaped_import_path } { disable_check_meta ( opts ) } ). { opts . escaped_attribute } "
2023-01-05 03:55:31 +03:00
)
2023-03-21 05:43:06 +03:00
def nix_prefetch ( opts : Options , attr : str ) - > str :
expr = f " { get_package ( opts ) } . { attr } "
2023-08-25 09:53:31 +03:00
extra_env : dict [ str , str ] = { }
tempdir : tempfile . TemporaryDirectory [ str ] | None = None
2023-03-26 05:16:09 +03:00
stderr = " "
2021-01-09 13:09:16 +03:00
if extra_env . get ( " XDG_RUNTIME_DIR " ) is None :
tempdir = tempfile . TemporaryDirectory ( )
extra_env [ " XDG_RUNTIME_DIR " ] = tempdir . name
try :
2022-11-10 18:56:26 +03:00
res = run (
[
" nix-build " ,
" --expr " ,
2022-11-14 17:48:28 +03:00
f ' let src = { expr } ; in (src.overrideAttrs or (f: src // f src)) (_: {{ outputHash = " " ; outputHashAlgo = " sha256 " ; }} ) ' ,
2023-01-05 03:55:31 +03:00
]
2023-01-08 18:35:32 +03:00
+ opts . extra_flags ,
2022-11-10 18:56:26 +03:00
extra_env = extra_env ,
2022-11-17 20:46:56 +03:00
stderr = subprocess . PIPE ,
2022-11-10 18:56:26 +03:00
check = False ,
)
stderr = res . stderr . strip ( )
got = " "
for line in stderr . split ( " \n " ) :
line = line . strip ( )
if line . startswith ( " got: " ) :
got = line . split ( " got: " ) [ 1 ] . strip ( )
break
2021-01-09 13:09:16 +03:00
finally :
if tempdir :
tempdir . cleanup ( )
2023-02-12 18:35:13 +03:00
if got == " " :
2023-03-26 05:16:09 +03:00
print ( stderr , file = sys . stderr )
raise UpdateError (
f " failed to retrieve hash when trying to update { opts . attribute } . { attr } "
)
2023-02-12 18:35:13 +03:00
else :
return got
2020-03-16 13:06:32 +03:00
2021-03-25 12:59:30 +03:00
def disable_check_meta ( opts : Options ) - > str :
2023-08-23 18:10:20 +03:00
return f ' (if (builtins.hasAttr " config " (builtins.functionArgs (import { opts . escaped_import_path } ))) then {{ config.checkMeta = false; overlays = []; }} else {{ }} ) '
2021-03-16 01:00:55 +03:00
2023-08-25 09:53:31 +03:00
def git_prefetch ( x : tuple [ str , tuple [ str , str ] ] ) - > tuple [ str , str ] :
2023-03-22 01:11:40 +03:00
rev , ( key , url ) = x
2023-03-21 05:43:06 +03:00
res = run ( [ " nix-prefetch-git " , url , rev , " --fetch-submodules " ] )
return key , to_sri ( json . loads ( res . stdout ) [ " sha256 " ] )
2022-12-08 20:11:00 +03:00
def update_src_hash ( opts : Options , filename : str , current_hash : str ) - > None :
2023-01-05 03:55:31 +03:00
target_hash = nix_prefetch ( opts , " src " )
2020-03-16 13:06:32 +03:00
replace_hash ( filename , current_hash , target_hash )
2022-09-17 03:11:22 +03:00
def update_go_modules_hash ( opts : Options , filename : str , current_hash : str ) - > None :
2023-07-12 18:23:23 +03:00
target_hash = nix_prefetch ( opts , " goModules " )
replace_hash ( filename , current_hash , target_hash )
def update_go_modules_hash_old ( opts : Options , filename : str , current_hash : str ) - > None :
2023-01-05 03:55:31 +03:00
target_hash = nix_prefetch ( opts , " go-modules " )
2020-03-16 13:06:32 +03:00
replace_hash ( filename , current_hash , target_hash )
2022-09-11 09:07:10 +03:00
def update_cargo_deps_hash ( opts : Options , filename : str , current_hash : str ) - > None :
2023-01-05 03:55:31 +03:00
target_hash = nix_prefetch ( opts , " cargoDeps " )
2020-05-08 12:21:06 +03:00
replace_hash ( filename , current_hash , target_hash )
2023-04-03 10:29:37 +03:00
def update_cargo_lock (
opts : Options , filename : str , dst : CargoLockInSource | CargoLockInStore
) - > None :
2023-03-21 05:43:06 +03:00
res = run (
[
" nix " ,
" build " ,
" --impure " ,
" --print-out-paths " ,
" --expr " ,
2023-04-03 05:56:36 +03:00
f """
2023-06-16 09:23:16 +03:00
{ get_package ( opts ) } . overrideAttrs ( old : { {
2023-04-03 05:56:36 +03:00
cargoDeps = null ;
postUnpack = ' '
2023-06-16 09:23:16 +03:00
cp - r " $sourceRoot/$ {{ old.cargoRoot or " . " }}/Cargo.lock " $ out
2023-04-03 05:56:36 +03:00
exit
' ' ;
outputs = [ " out " ] ;
2024-02-24 02:53:11 +03:00
separateDebugInfo = false ;
2023-04-03 05:56:36 +03:00
} } )
""" ,
2023-03-21 05:43:06 +03:00
]
+ opts . extra_flags ,
)
2023-04-03 05:56:36 +03:00
src = Path ( res . stdout . strip ( ) )
2023-03-21 05:43:06 +03:00
if not src . is_file ( ) :
return
2023-04-03 05:56:36 +03:00
with open ( src , " rb " ) as f :
2023-04-03 10:26:16 +03:00
if isinstance ( dst , CargoLockInSource ) :
with open ( dst . path , " wb " ) as fdst :
2023-04-03 05:56:36 +03:00
shutil . copyfileobj ( f , fdst )
f . seek ( 0 )
hashes = { }
2023-03-21 05:43:06 +03:00
lock = tomllib . load ( f )
regex = re . compile ( r " git \ +([^?]+)( \ ?(rev|tag|branch)=.*)?#(.*) " )
git_deps = { }
for pkg in lock [ " package " ] :
if source := pkg . get ( " source " ) :
if match := regex . fullmatch ( source ) :
2023-03-22 01:11:40 +03:00
rev = match [ 4 ]
if rev not in git_deps :
git_deps [ rev ] = f " { pkg [ ' name ' ] } - { pkg [ ' version ' ] } " , match [ 1 ]
2023-03-21 05:43:06 +03:00
for k , v in ThreadPoolExecutor ( ) . map ( git_prefetch , git_deps . items ( ) ) :
hashes [ k ] = v
with fileinput . FileInput ( filename , inplace = True ) as f :
short = re . compile ( r " ( \ s*)cargoLock \ .lockFile \ s*= \ s*(.+) \ s*; \ s* " )
expanded = re . compile ( r " ( \ s*)lockFile \ s*= \ s*(.+) \ s*; \ s* " )
for line in f :
if match := short . fullmatch ( line ) :
indent = match [ 1 ]
path = match [ 2 ]
print ( f " { indent } cargoLock = {{ " )
print ( f " { indent } lockFile = { path } ; " )
print_hashes ( hashes , f " { indent } " )
print ( f " { indent } }} ; " )
for line in f :
print ( line , end = " " )
return
elif match := expanded . fullmatch ( line ) :
indent = match [ 1 ]
path = match [ 2 ]
print ( line , end = " " )
print_hashes ( hashes , indent )
brace = 0
for line in f :
for c in line :
if c == " { " :
brace - = 1
if c == " } " :
brace + = 1
if brace == 1 :
print ( line , end = " " )
for line in f :
print ( line , end = " " )
return
else :
print ( line , end = " " )
2023-12-09 17:07:39 +03:00
def update_composer_deps_hash ( opts : Options , filename : str , current_hash : str ) - > None :
target_hash = nix_prefetch ( opts , " composerRepository " )
replace_hash ( filename , current_hash , target_hash )
2023-08-25 09:53:31 +03:00
def print_hashes ( hashes : dict [ str , str ] , indent : str ) - > None :
2023-03-21 05:43:06 +03:00
if not hashes :
return
print ( f " { indent } outputHashes = {{ " )
for k , v in hashes . items ( ) :
print ( f ' { indent } " { k } " = " { v } " ; ' )
print ( f " { indent } }} ; " )
2022-11-10 18:56:55 +03:00
def update_npm_deps_hash ( opts : Options , filename : str , current_hash : str ) - > None :
2023-01-05 03:55:31 +03:00
target_hash = nix_prefetch ( opts , " npmDeps " )
2020-03-16 13:06:32 +03:00
replace_hash ( filename , current_hash , target_hash )
2021-10-08 22:20:21 +03:00
def update_yarn_deps_hash ( opts : Options , filename : str , current_hash : str ) - > None :
target_hash = nix_prefetch ( opts , " offlineCache " )
replace_hash ( filename , current_hash , target_hash )
2021-01-18 17:53:27 +03:00
def update_version (
2021-01-25 02:30:37 +03:00
package : Package , version : str , preference : VersionPreference , version_regex : str
2021-01-18 17:53:27 +03:00
) - > bool :
2021-01-25 02:30:37 +03:00
if preference == VersionPreference . FIXED :
2022-01-15 15:44:02 +03:00
new_version = Version ( version )
2021-01-25 02:30:37 +03:00
else :
2022-11-24 20:33:58 +03:00
if not package . parsed_url :
raise UpdateError ( " Could not find a url in the derivations src attribute " )
2022-11-29 20:44:09 +03:00
version_prefix = " "
2022-01-15 15:50:01 +03:00
if preference != VersionPreference . BRANCH :
branch = None
2022-11-29 20:44:09 +03:00
if package . rev and package . rev . endswith ( package . old_version ) :
version_prefix = package . rev . removesuffix ( package . old_version )
2022-01-15 15:50:01 +03:00
elif version == " branch " :
# fallback
2022-01-17 01:07:52 +03:00
branch = " HEAD "
2022-01-15 15:50:01 +03:00
else :
assert version . startswith ( " branch= " )
branch = version [ 7 : ]
new_version = fetch_latest_version (
2022-11-29 20:44:09 +03:00
package . parsed_url ,
preference ,
version_regex ,
branch ,
package . rev ,
version_prefix ,
2022-01-15 15:50:01 +03:00
)
2020-12-01 11:30:17 +03:00
package . new_version = new_version
position = package . version_position
2022-01-15 15:44:02 +03:00
if new_version . number == package . old_version and position :
2020-12-01 11:30:17 +03:00
recovered_version = old_version_from_git (
2022-01-15 15:44:02 +03:00
position . file , position . line , new_version . number
2020-12-01 11:30:17 +03:00
)
if recovered_version :
package . old_version = recovered_version
2020-11-28 00:10:38 +03:00
return False
2022-11-24 20:33:58 +03:00
if package . parsed_url :
2023-05-12 19:52:30 +03:00
if package . parsed_url . netloc == " crates.io " :
parts = package . parsed_url . path . split ( " / " )
package . diff_url = (
f " https://diff.rs/ { parts [ 4 ] } / { package . old_version } / { new_version . number } "
)
elif package . parsed_url . netloc == " github.com " :
2022-11-24 20:33:58 +03:00
_ , owner , repo , * _ = package . parsed_url . path . split ( " / " )
2023-01-28 01:30:52 +03:00
package . diff_url = f " https://github.com/ { owner } / { repo . removesuffix ( ' .git ' ) } /compare/ { package . rev } ... { new_version . rev or new_version . number } "
2023-01-21 23:31:30 +03:00
elif package . parsed_url . netloc in [ " codeberg.org " , " gitea.com " , " notabug.org " ] :
_ , owner , repo , * _ = package . parsed_url . path . split ( " / " )
package . diff_url = f " https:// { package . parsed_url . netloc } / { owner } / { repo } /compare/ { package . rev } ... { new_version . rev or new_version . number } "
2022-11-24 20:33:58 +03:00
elif GITLAB_API . match ( package . parsed_url . geturl ( ) ) and package . src_homepage :
package . diff_url = f " { package . src_homepage } -/compare/ { package . rev } ... { new_version . rev or new_version . number } "
2023-11-21 20:14:13 +03:00
elif package . parsed_url . netloc in [ " bitbucket.org " , " bitbucket.io " ] :
_ , owner , repo , * _ = package . parsed_url . path . split ( " / " )
package . diff_url = f " https:// { package . parsed_url . netloc } / { owner } / { repo } /branches/compare/ { new_version . rev or new_version . number } %0D { package . rev } "
2022-11-24 20:33:58 +03:00
2020-11-28 00:10:38 +03:00
return replace_version ( package )
2020-12-01 11:30:17 +03:00
2020-03-24 16:25:17 +03:00
def update ( opts : Options ) - > Package :
2020-03-23 13:38:04 +03:00
package = eval_attr ( opts )
2020-03-16 13:06:32 +03:00
2022-11-28 18:43:41 +03:00
if package . has_update_script and opts . use_update_script :
run (
[
" nix-shell " ,
path . join ( opts . import_path , " maintainers/scripts/update.nix " ) ,
" --argstr " ,
" package " ,
opts . attribute ,
] ,
stdout = None ,
)
new_package = eval_attr ( opts )
package . new_version = Version ( new_package . old_version , rev = new_package . rev )
return package
2020-11-28 00:10:38 +03:00
update_hash = True
2021-08-26 16:00:20 +03:00
if opts . version_preference != VersionPreference . SKIP :
2021-01-18 17:53:27 +03:00
update_hash = update_version (
2021-08-26 16:00:20 +03:00
package , opts . version , opts . version_preference , opts . version_regex
2021-01-18 17:53:27 +03:00
)
2020-03-16 13:06:32 +03:00
2021-08-26 14:08:18 +03:00
if package . hash and update_hash :
2020-11-28 00:10:38 +03:00
update_src_hash ( opts , package . filename , package . hash )
2020-03-16 13:06:32 +03:00
2021-08-26 14:08:18 +03:00
# if no package.hash was provided we just update the other hashes unconditionally
if update_hash or not package . hash :
2023-04-10 00:11:05 +03:00
if package . go_modules :
update_go_modules_hash ( opts , package . filename , package . go_modules )
2020-03-16 13:06:32 +03:00
2023-07-12 18:23:23 +03:00
if package . go_modules_old :
update_go_modules_hash_old ( opts , package . filename , package . go_modules_old )
2022-09-11 09:07:10 +03:00
if package . cargo_deps :
update_cargo_deps_hash ( opts , package . filename , package . cargo_deps )
2020-03-24 16:25:17 +03:00
2023-04-03 10:26:16 +03:00
if isinstance ( package . cargo_lock , CargoLockInSource ) or isinstance (
package . cargo_lock , CargoLockInStore
) :
2023-03-21 05:43:06 +03:00
update_cargo_lock ( opts , package . filename , package . cargo_lock )
2023-12-09 17:07:39 +03:00
if package . composer_deps :
update_composer_deps_hash ( opts , package . filename , package . composer_deps )
2022-11-10 18:56:55 +03:00
if package . npm_deps :
update_npm_deps_hash ( opts , package . filename , package . npm_deps )
2020-03-24 16:25:17 +03:00
2021-10-08 22:20:21 +03:00
if package . yarn_deps :
update_yarn_deps_hash ( opts , package . filename , package . yarn_deps )
2020-03-24 16:25:17 +03:00
return package