diff --git a/share/fsl/sbin/update_fsl_package b/share/fsl/sbin/update_fsl_package index b4cb764a1ea82a1e898133d293c505b4823b8c22..b0396bd1aa39f54e04a47897f05d26bdbae50585 100755 --- a/share/fsl/sbin/update_fsl_package +++ b/share/fsl/sbin/update_fsl_package @@ -54,6 +54,28 @@ log = logging.getLogger(__name__) PUBLIC_FSL_CHANNEL = 'https://fsl.fmrib.ox.ac.uk/fsldownloads/fslconda/public/' INTERNAL_FSL_CHANNEL = 'https://fsl.fmrib.ox.ac.uk/fsldownloads/fslconda/internal/' +EXTERNALLY_HOSTED_PACKAGES = { + 'fslpy' : 'conda-forge', + 'fsleyes-props' : 'conda-forge', + 'fsleyes-widgets' : 'conda-forge', + 'fsleyes' : 'conda-forge', + 'fmrib-unpack' : 'conda-forge', + 'file-tree' : 'conda-forge', + 'file-tree-fsl' : 'conda-forge', + 'spec2nii' : 'conda-forge', +} +"""List of packages which are considered to be part of FSL, but which are hosted +on an external channel (most likely conda-forge). These packages should still be +considered for updates. + +Version information for these packages is only retrieved if they are +specifically requested by the user, or if the --external option is used. +This is because downloading and parsing the conda-forge channeldata information +takes a very long time. + +The values in this dictionary can either be a channel name on +https://anaconda.org, or a fully qualified channel URL. +""" def conda(cmd : str, capture_output=True, **kwargs) -> str: @@ -84,6 +106,7 @@ def conda(cmd : str, capture_output=True, **kwargs) -> str: return result.stdout +@ft.lru_cache def identify_platform() -> str: """Figures out what platform we are running on. Returns a platform identifier string - one of: @@ -243,17 +266,66 @@ def query_installed_packages() -> Dict[str, Package]: pkgs = {} # We are only interested in packages - # hosted on the FSL conda channels + # hosted on the FSL conda channels, + # or those listed in the externally + # hosted package list. for pkg in info: - if pkg['base_url'].rstrip() in channels: + if pkg['base_url'].rstrip() in channels or \ + pkg['name'] in EXTERNALLY_HOSTED_PACKAGES: pkgs[pkg['name']] = Package(pkg['name'], pkg['version'], pkg['channel'], pkg['platform']) - return pkgs +@ft.lru_cache +def download_external_package_metadata(pkgname : str, + channel_url : str, + development : bool) -> Optional[Package]: + """Downloads metadata about one externally hosted package. The returned + Package object does not contain platform or dependency information. + """ + + # if channel_url is a full url, we + # download the full channel metdata + # and look up the package + if any(channel_url.startswith(p) for p in ('https:', 'http:', 'file:')): + chandata = download_channel_metadata(channel_url) + pkgs = identify_packages([chandata], [pkgname], development) + return pkgs.get(pkgname, [None])[-1] + + # Othrerwise channel_url is the name + # of an anaconda.org channel - we + # just retrieve information about + # the package + channel = channel_url + api_url = f'https://api.anaconda.org/package/{channel}/' + channel_url = f'https://anaconda.org/{channel}/' + + try: + meta = http_request(f'{api_url}{pkgname}') + except Exception: + log.debug(f'Package lookup failed [{pkgname} : {channel_url}]') + return None + + # Find the latest available + # version for this platform + thisplat = ('noarch', identify_platform()) + + for finfo in meta['files'][::-1]: + version = finfo['version'] + platform = finfo['attrs']['subdir'] + isdev = development or ('dev' not in version) + + if isdev and (platform in thisplat): + return Package(pkgname, version, channel_url, platform) + + # No suitable version available + else: + return None + + @ft.lru_cache def download_channel_metadata(channel_url : str, **kwargs) -> Tuple[Dict, Dict]: """Downloads information about packages hosted at the given conda channel. @@ -276,7 +348,7 @@ def download_channel_metadata(channel_url : str, **kwargs) -> Tuple[Dict, Dict]: Keyword arguments are passed through to the http_request function. """ - thisplat = identify_platform() + thisplat = ('noarch', identify_platform()) # Load channel and platform metadata - the # first gives us a list of all packages that @@ -291,7 +363,7 @@ def download_channel_metadata(channel_url : str, **kwargs) -> Tuple[Dict, Dict]: # only consider packages # relevant to this platform for platform in chandata['subdirs']: - if platform in ('noarch', thisplat): + if platform in thisplat: purl = f'{channel_url}/{platform}/repodata.json' platdata[platform] = http_request(purl)