├── nvidia.conf ├── CONTRIBUTING ├── yum-plugin-nvidia.spec ├── CONTRIBUTING.md ├── dnf-plugin-nvidia.spec ├── nvidia-dnf.py ├── README.md ├── nvidia-yum.py └── LICENSE /nvidia.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | enabled = 1 3 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /yum-plugin-nvidia.spec: -------------------------------------------------------------------------------- 1 | Name: yum-plugin-nvidia 2 | Version: 0.5 3 | Release: 1%{?dist} 4 | Summary: YUM plugin to handle Nvidia driver module packages 5 | 6 | Group: System/Kernel 7 | License: GPLv2+ 8 | URL: http://git.engineering.redhat.com/git/users/tbaeder/yum-plugin-nvidia.git/ 9 | BuildArch: noarch 10 | Source0: nvidia-yum.py 11 | Source1: nvidia.conf 12 | 13 | Requires: python 14 | 15 | %description 16 | The nvidia yum plugin helps keeping the necessary nvidia kernel module 17 | packages installed at all times. 18 | 19 | 20 | %build 21 | # Empty 22 | 23 | %prep 24 | # Empty 25 | 26 | %install 27 | mkdir -p %{buildroot}%{_sysconfdir}/yum/ 28 | mkdir -p %{buildroot}%{_sysconfdir}/yum/pluginconf.d/ 29 | install -m 644 %{SOURCE1} %{buildroot}%{_sysconfdir}/yum/pluginconf.d/ 30 | 31 | mkdir -p %{buildroot}%{_prefix}/lib/yum-plugins/ 32 | install -m 644 %{SOURCE0} %{buildroot}%{_prefix}/lib/yum-plugins/nvidia.py 33 | 34 | %files 35 | %{_prefix}/lib/yum-plugins/nvidia.py* 36 | %config(noreplace) %{_sysconfdir}/yum/pluginconf.d/nvidia.conf 37 | 38 | %changelog 39 | * Tue Sep 03 2019 Timm Bäder 0.5-1 40 | - Print warning when kmod package not found 41 | 42 | * Mon Jun 24 2019 Timm Bäder 0.4-1 43 | - Remove debugging line 44 | 45 | * Wed May 15 2019 Timm Bäder 0.3-1 46 | - Replace startsWith with regex 47 | 48 | * Tue Apr 16 2019 Timm Bäder 0.2-1 49 | - Stop yum from automatically updating kmod packages 50 | 51 | * Thu Jun 21 2018 Timm Bäder 0.1-1 52 | - Initial revision 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to this project 2 | 3 | Want to contribute to this project? Awesome! 4 | We only require you to sign your work, the below section describes this! 5 | 6 | ## Sign your work 7 | 8 | The sign-off is a simple line at the end of the explanation for the patch. Your 9 | signature certifies that you wrote the patch or otherwise have the right to pass 10 | it on as an open-source patch. The rules are pretty simple: if you can certify 11 | the below (from [developercertificate.org](http://developercertificate.org/)): 12 | 13 | ``` 14 | Developer Certificate of Origin 15 | Version 1.1 16 | 17 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 18 | 1 Letterman Drive 19 | Suite D4700 20 | San Francisco, CA, 94129 21 | 22 | Everyone is permitted to copy and distribute verbatim copies of this 23 | license document, but changing it is not allowed. 24 | 25 | Developer's Certificate of Origin 1.1 26 | 27 | By making a contribution to this project, I certify that: 28 | 29 | (a) The contribution was created in whole or in part by me and I 30 | have the right to submit it under the open source license 31 | indicated in the file; or 32 | 33 | (b) The contribution is based upon previous work that, to the best 34 | of my knowledge, is covered under an appropriate open source 35 | license and I have the right under that license to submit that 36 | work with modifications, whether created in whole or in part 37 | by me, under the same open source license (unless I am 38 | permitted to submit under a different license), as indicated 39 | in the file; or 40 | 41 | (c) The contribution was provided directly to me by some other 42 | person who certified (a), (b) or (c) and I have not modified 43 | it. 44 | 45 | (d) I understand and agree that this project and the contribution 46 | are public and that a record of the contribution (including all 47 | personal information I submit with it, including my sign-off) is 48 | maintained indefinitely and may be redistributed consistent with 49 | this project or the open source license(s) involved. 50 | ``` 51 | 52 | Then you just add a line to every git commit message: 53 | 54 | Signed-off-by: Joe Smith 55 | 56 | Use your real name (sorry, no pseudonyms or anonymous contributions.) 57 | 58 | If you set your `user.name` and `user.email` git configs, you can sign your 59 | commit automatically with `git commit -s`. 60 | -------------------------------------------------------------------------------- /dnf-plugin-nvidia.spec: -------------------------------------------------------------------------------- 1 | Name: dnf-plugin-nvidia 2 | Version: 2.1 3 | Release: 2%{?dist} 4 | Summary: DNF plugin needed to remove old kernel modules 5 | License: MIT 6 | BuildArch: noarch 7 | 8 | Source0: nvidia-dnf.py 9 | 10 | BuildRequires: python3-devel 11 | 12 | Requires: python3-dnf 13 | 14 | %description 15 | When using precompiled kernel modules, this DNF plugin prevents kernel changes 16 | if there is no matching precompiled kernel module package available. 17 | 18 | It also also removes kernel module packages of outdated driver versions from 19 | the system. 20 | 21 | %install 22 | install -D -m 644 %{SOURCE0} %{buildroot}%{python3_sitelib}/dnf-plugins/nvidia.py 23 | 24 | %files 25 | %{python3_sitelib}/dnf-plugins/* 26 | 27 | %changelog 28 | * Thu Jun 13 2024 Simone Caronni - 2.1-2 29 | - Fix error when running without any driver package installed. 30 | - Print all installed kernels when running standalone, the latest installed 31 | kernel might not be the running one. 32 | 33 | * Wed Jun 05 2024 Simone Caronni - 2.1-1 34 | - Consider *all* kernel subpackages when locking kernel upgrades. 35 | - The plugin now locks the kernel both in upgrade and downgrade scenarios. 36 | - Print information in nicer format when executing standalone. 37 | - Print all excluded kernel packages and subpackages from the current repository 38 | information when executing standalone. 39 | - Consider both desktop, headless (compute only) and combined when checking for 40 | drivers. 41 | 42 | * Fri Apr 12 2024 Simone Caronni - 2.0-2 43 | - Clean up SPEC file, make it build in mock. 44 | - Merge https://github.com/NVIDIA/yum-packaging-nvidia-plugin/pull/9. 45 | 46 | * Fri Apr 30 2021 Kevin Mittman 2.1-1 47 | - Debug mode: avoid index out-of-bounds if no kernel installed (in container) 48 | 49 | * Mon Sep 28 2020 Kevin Mittman 2.0-1 50 | - Handle upgrade scenario when new branch is promoted to latest 51 | - Fix try-catch as per Timm's comments 52 | - Cleanup notice message 53 | 54 | * Thu Sep 17 2020 Kevin Mittman 1.9-1 55 | - Do not block kernel updates if DKMS stream kmod package is installed 56 | - Retrieve k_corepkg hawkey package object rather than string 57 | - Expose plugin as DNF command (for debugging) 58 | - Print verbose output when running as a command 59 | 60 | * Wed Sep 9 2020 Kevin Mittman 1.8-1 61 | - Block kernel-core updates that do not have a matching kmod package 62 | 63 | * Tue Sep 8 2020 Kevin Mittman 1.7-1 64 | - Pass %_python_sitelib as variable to avoid hard-coding python3.6 65 | 66 | * Tue Oct 29 2019 Timm Bäder 1.6-1 67 | - Block kernel updates that do not have a matching kmod package 68 | - Use %python3_sitelib 69 | 70 | * Mon Jun 17 2019 Timm Bäder 1.1-1 71 | - Exclude dkms packages 72 | 73 | * Tue May 28 2019 Timm Bäder 1.0-1 74 | - Initial .spec 75 | -------------------------------------------------------------------------------- /nvidia-dnf.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import unicode_literals 3 | 4 | import os 5 | import shutil 6 | from functools import cmp_to_key 7 | 8 | from dnf.cli.option_parser import OptionParser 9 | import dnf 10 | import dnf.cli 11 | import dnf.sack 12 | import libdnf.transaction 13 | 14 | DESKTOP_PKG_NAME = 'nvidia-driver' 15 | COMPUTE_PKG_NAME = 'nvidia-driver-cuda' 16 | KERNEL_PKG_NAME = 'kernel-core' 17 | KMOD_PKG_PREFIX = 'kmod-nvidia' 18 | 19 | def is_kmod_pkg(pkg): 20 | return pkg.name.startswith(KMOD_PKG_PREFIX) and 'dkms' not in pkg.name 21 | 22 | def remove_release_dist(release): 23 | return release[0:release.rfind('.')] 24 | 25 | def evr_key(po, sack): 26 | func = cmp_to_key(sack.evr_cmp) 27 | return func(str(po.epoch) + ':' + str(po.version) + '-' + str(po.release)) 28 | 29 | def ver_cmp_pkgs(sack, po1, po2): 30 | return sack.evr_cmp(str(po1.epoch) + ':' + str(po1.version) + '-' + str(po1.release), 31 | str(po2.epoch) + ':' + str(po2.version) + '-' + str(po2.release)); 32 | 33 | def revive_msg(var, msg, val = ''): 34 | if var is not None: 35 | print(msg) 36 | 37 | return val 38 | 39 | 40 | class NvidiaPlugin(dnf.Plugin): 41 | name = 'nvidia' 42 | 43 | def __init__(self, base, cli): 44 | super(NvidiaPlugin, self).__init__(base, cli) 45 | self.base = base 46 | self.cli = cli 47 | 48 | def sack(self, debug = None): 49 | # run as command 50 | if debug == True: 51 | base = self.base() 52 | base.read_all_repos() 53 | base.fill_sack() 54 | sack = base.sack 55 | # run as plugin 56 | else: 57 | sack = self.base.sack 58 | 59 | # check installed 60 | installed_drivers = sack.query().installed().filter(name = [DESKTOP_PKG_NAME, COMPUTE_PKG_NAME]) 61 | installed_kernel = list(sack.query().installed().filter(name = KERNEL_PKG_NAME)) 62 | installed_modules = list(sack.query().installed().filter(name__substr = KMOD_PKG_PREFIX)) 63 | 64 | # driver not installed 65 | if not installed_drivers and debug is None: 66 | return 67 | 68 | # container/chroot 69 | if not installed_kernel and debug is None: 70 | return 71 | 72 | # The most recent installed kernel package 73 | installed_kernels = sorted(installed_kernel, reverse = True, key = lambda p: evr_key(p, sack)) 74 | if len(installed_kernels) > 0: 75 | installed_kernel = installed_kernels[0] 76 | 77 | available_kernels = sack.query().available().filter(name = KERNEL_PKG_NAME) 78 | available_drivers = sack.query().available().filter(name = [DESKTOP_PKG_NAME, COMPUTE_PKG_NAME]) 79 | dkms_kmod_modules = sack.query().available().filter(name__substr = "dkms") 80 | available_modules = sack.query().available().filter(name__substr = KMOD_PKG_PREFIX).difference(dkms_kmod_modules) 81 | 82 | # Print debugging if running from CLI 83 | if installed_kernels: 84 | string_kernels = '\n '.join([str(elem) for elem in installed_kernels]) 85 | revive_msg(debug, '\nInstalled kernel(s):\n ' + str(string_kernels)) 86 | 87 | if installed_modules: 88 | string_modules = '\n '.join([str(elem) for elem in installed_modules]) 89 | revive_msg(debug, '\nInstalled kmod(s):\n ' + str(string_modules)) 90 | 91 | if available_kernels: 92 | string_kernels = '\n '.join([str(elem) for elem in available_kernels]) 93 | revive_msg(debug, '\nAvailable kernel(s):\n ' + str(string_kernels)) 94 | 95 | if available_drivers: 96 | string_drivers = '\n '.join([str(elem) for elem in available_drivers]) 97 | revive_msg(debug, '\nAvailable driver(s):\n ' + str(string_drivers)) 98 | 99 | if available_modules: 100 | string_all_modules = '\n '.join([str(elem) for elem in available_modules]) 101 | revive_msg(debug, '\nAvailable kmod(s):\n ' + str(string_all_modules)) 102 | 103 | revive_msg(debug, '') 104 | 105 | # DKMS stream enabled 106 | if installed_modules and 'dkms' in string_modules: 107 | return 108 | 109 | # Installed driver 110 | try: 111 | driver = installed_drivers[0] 112 | except: 113 | return 114 | 115 | # Exclude all available kernels which do not have a kmod package 116 | for kernelpkg in available_kernels: 117 | 118 | # Iterate through drivers in stream 119 | kmod_pkg = None 120 | for a_driver in available_drivers: 121 | # Get package name 122 | kmod_pkg_name = KMOD_PKG_PREFIX + '-' + str(a_driver.version) + '-' + str(kernelpkg.version) + '-' + str(remove_release_dist(kernelpkg.release)) 123 | 124 | # Append object 125 | if kmod_pkg is not None: 126 | kmod_pkg = sack.query().available().filter(name = kmod_pkg_name, version = a_driver.version).union(kmod_pkg) 127 | else: 128 | kmod_pkg = sack.query().available().filter(name = kmod_pkg_name, version = a_driver.version) 129 | 130 | # kmod for kernel and driver combination not available 131 | if not kmod_pkg: 132 | 133 | # Assemble a list of all packages that are built from the same kernel source rpm 134 | all_rpms_of_kernel = sack.query().available().filter(release = kernelpkg.release) 135 | 136 | string_all_rpms_of_kernel = '\n '.join([str(elem) for elem in all_rpms_of_kernel]) 137 | revive_msg(debug, 'Excluded kernel packages during update (' + str(kernelpkg.version) + '-' + str(kernelpkg.release) + '):\n ' + str(string_all_rpms_of_kernel)) 138 | revive_msg(debug, '') 139 | 140 | # Exclude packages 141 | if debug is None: 142 | try: 143 | sack.add_excludes(all_rpms_of_kernel) 144 | print('NVIDIA driver: filtering kernel ' + str(kernelpkg.version) + '-' + str(kernelpkg.release) + ', no precompiled modules available for version ' + str(driver.epoch) + ':' + str(driver.version)) 145 | except Exception as error: 146 | print('WARNING: kernel exclude error', error) 147 | 148 | def resolved(self): 149 | transaction = self.base.transaction 150 | 151 | for pkg in transaction.remove_set: 152 | if pkg.name == DESKTOP_PKG_NAME or pkg.name == COMPUTE_PKG_NAME: 153 | # We are removing a driver package, through an 154 | # actual remove or an upgrade. Remove all 155 | # kmod packages belonging to it as well. 156 | installed_kmods = sack.query().installed().filter(version = pkg.version) 157 | 158 | # The above query only selects by version since we don't know 159 | # the exact name of the kmod package. Look here for them by prefix 160 | # and remove them if they match the version of the driver 161 | # we're removing right now. 162 | for kmod in installed_kmods: 163 | if is_kmod_pkg(kmod): 164 | transaction.add_erase(kmod) 165 | 166 | 167 | @dnf.plugin.register_command 168 | class NvidiaPluginCommand(dnf.cli.Command): 169 | aliases = ('nvidia-plugin',) 170 | summary = 'Helper plugin for DNF to manage precompiled NVIDIA driver streams' 171 | 172 | def run(self): 173 | nvPlugin = NvidiaPlugin(dnf.Base, dnf.cli.Cli) 174 | nvPlugin.sack(True) 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yum packaging nvidia plugin 2 | 3 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 4 | [![Contributing](https://img.shields.io/badge/Contributing-Developer%20Certificate%20of%20Origin-violet)](https://developercertificate.org) 5 | 6 | ## Overview 7 | 8 | Packaging templates for `yum` and `dnf` based Linux distros for Python plugin to manage NVIDIA driver precompiled kernel module packages. 9 | 10 | The `main` branch contains this README and a sample build script. The `.spec` and `nvidia.py` files can be found in the appropriate [rhel7](../../tree/rhel7), [rhel8](../../tree/rhel8), and [fedora](../../tree/fedora) branches. 11 | 12 | ## Table of Contents 13 | 14 | - [Overview](#Overview) 15 | - [Deliverables](#Deliverables) 16 | - [Prerequisites](#Prerequisites) 17 | * [Clone this git repository](#Clone-this-git-repository) 18 | * [Install build dependencies](#Install-build-dependencies) 19 | - [Demo](#Demo) 20 | - [Building with script](#Building-with-script) 21 | * [Fetch script from main branch](#Fetch-script-from-main-branch) 22 | * [Usage](#Usage) 23 | - [Building Manually](#Building-Manually) 24 | * [Packaging plugin](#Packaging-plugin) 25 | * [Sign RPM packages with GPG signing key](#Sign-RPM-packages-with-GPG-signing-key) 26 | - [Blocking kernel updates](#Blocking-kernel-updates) 27 | * [Debugging](#Debugging) 28 | - [Other NVIDIA driver packages](#Other-NVIDIA-driver-packages) 29 | * [Precompiled kernel modules](#Precompiled-kernel-modules) 30 | - [Contributing](#Contributing) 31 | 32 | 33 | ## Deliverables 34 | 35 | This repo contains the `.spec` file used to build the following **RPM** packages: 36 | 37 | * **RHEL8** or **Fedora** 38 | ```shell 39 | dnf-plugin-nvidia-${version}-${rel}.${dist}.noarch.rpm 40 | > ex: dnf-plugin-nvidia-2.0-1.el8.noarch.rpm 41 | ``` 42 | 43 | * **RHEL7** 44 | ```shell 45 | yum-plugin-nvidia-${version}-${rel}.${dist}.noarch.rpm 46 | > ex: yum-plugin-nvidia-0.5-1.el7.noarch.rpm 47 | ``` 48 | 49 | ## Prerequisites 50 | 51 | ### Clone this git repository: 52 | 53 | Supported branches: `rhel7`, `rhel8` & `fedora` 54 | 55 | ```shell 56 | git clone -b ${branch} https://github.com/NVIDIA/yum-packaging-nvidia-plugin 57 | > ex: git clone -b rhel8 https://github.com/NVIDIA/yum-packaging-nvidia-plugin 58 | ``` 59 | 60 | ### Install build dependencies 61 | > *note:* these are only needed for building not installation 62 | 63 | ```shell 64 | # Python 65 | yum install python36 66 | 67 | # Packaging 68 | yum install rpm-build 69 | ``` 70 | 71 | 72 | ## Demo 73 | 74 | ![Demo](https://developer.download.nvidia.com/compute/github-demos/yum-packaging-nvidia-plugin/demo.gif) 75 | 76 | [![asciinema](https://img.shields.io/badge/Play%20Video-asciinema-red)](https://developer.download.nvidia.com/compute/github-demos/yum-packaging-nvidia-plugin/demo-ascii/) 77 | 78 | 79 | ## Building with script 80 | 81 | ### Fetch script from `main` branch 82 | 83 | ```shell 84 | cd yum-packaging-nvidia-plugin 85 | git checkout remotes/origin/main -- build.sh 86 | ``` 87 | 88 | ### Usage 89 | 90 | ```shell 91 | ./build.sh 92 | ``` 93 | 94 | 95 | ## Building Manually 96 | 97 | ### Packaging plugin 98 | 99 | #### RHEL8 or Fedora 100 | 101 | ```shell 102 | mkdir BUILD BUILDROOT RPMS SRPMS SOURCES SPECS 103 | cp nvidia-dnf.py SOURCES/ 104 | cp dnf-plugin-nvidia.spec SPECS/ 105 | 106 | rpmbuild \ 107 | --define "%_topdir $(pwd)" \ 108 | --define "debug_package %{nil}" \ 109 | --define "_python_sitelib $pythonLocation" \ 110 | -v -bb SPECS/dnf-plugin-nvidia.spec 111 | 112 | > ex: rpmbuild \ 113 | --define "%_topdir $(pwd)" \ 114 | --define "debug_package %{nil}" \ 115 | --define "_python_sitelib /usr/lib/python3.6/site-packages" \ 116 | -v -bb SPECS/dnf-plugin-nvidia.spec 117 | ``` 118 | 119 | #### RHEL7 120 | 121 | ```shell 122 | mkdir BUILD BUILDROOT RPMS SRPMS SOURCES SPECS 123 | cp nvidia.conf SOURCES/ 124 | cp nvidia-yum.py SOURCES/ 125 | cp yum-plugin-nvidia.spec SPECS/ 126 | 127 | rpmbuild \ 128 | --define "%_topdir $(pwd)" \ 129 | --define "debug_package %{nil}" \ 130 | -v -bb SPECS/yum-plugin-nvidia.spec 131 | ``` 132 | 133 | ### Sign RPM package(s) with GPG signing key 134 | 135 | If one does not already exist, [generate a GPG key pair](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/packaging_and_distributing_software/advanced-topics) 136 | 137 | ```shell 138 | gpg --generate-key 139 | ``` 140 | 141 | Set `$gpgKey` to secret key ID. 142 | 143 | ```shell 144 | gpgArgs="/usr/bin/gpg --force-v3-sigs --digest-algo=sha512 --no-verbose --no-armor --no-secmem-warning" 145 | for package in RPMS/noarch/*-plugin-nvidia*.rpm; do 146 | rpm \ 147 | --define "%_signature gpg" \ 148 | --define "%_gpg_name $gpgKey" \ 149 | --define "%__gpg /usr/bin/gpg" \ 150 | --define "%_gpg_digest_algo sha512" \ 151 | --define "%_binary_filedigest_algorithm 10" \ 152 | --define "%__gpg_sign_cmd %{__gpg} $gpgArgs -u %{_gpg_name} \ 153 | -sbo %{__signature_filename} %{__plaintext_filename}" \ 154 | --addsign "$package"; 155 | done 156 | ``` 157 | 158 | 159 | ## Blocking kernel updates 160 | 161 | * **RHEL8** or **Fedora** 162 | Kernel updates will be blocked in the absence of the availability of a compatible kmod package for that kernel and driver combination. 163 | 164 | The `kernel` and `kernel-core` packages will be removed from such `dnf` transactions and an error message will be printed, ex: 165 | 166 | > NOTE: Skipping kernel installation since no kernel module package kmod-nvidia-450.51.06-4.18.0-193.19.1 for kernel 4.18.0-193.19.1 and NVIDIA driver 450.51.06-1.el8.x86_64 could be found 167 | 168 | 169 | ### Debugging 170 | 171 | * **RHEL8** or **Fedora** 172 | Heuristic information can be printed via CLI, such as installed kernel, installed kmod packages, available kernels, available drivers, and available kmod packages. 173 | 174 | ```shell 175 | dnf nvidia-plugin 176 | ``` 177 | > ex: 178 | **installed kernel:** kernel-4.18.0-193.14.3.el8_2.x86_64 179 | **installed kmod(s):** kmod-nvidia-450.51.06-4.18.0-193.14.3-3:450.51.06-2.el8_2.x86_64 180 | **available kernel(s):** kernel-4.18.0-80.el8.x86_64 kernel-4.18.0-80.1.2.el8_0.x86_64 kernel-4.18.0-80.4.2.el8_0.x86_64 kernel-4.18.0-80.11.2.el8_0.x86_64 kernel-4.18.0-80.7.1.el8_0.x86_64 kernel-4.18.0-80.7.2.el8_0.x86_64 kernel-4.18.0-80.11.1.el8_0.x86_64 kernel-4.18.0-147.0.2.el8_1.x86_64 kernel-4.18.0-147.5.1.el8_1.x86_64 kernel-4.18.0-147.8.1.el8_1.x86_64 kernel-4.18.0-147.3.1.el8_1.x86_64 kernel-4.18.0-147.el8.x86_64 kernel-4.18.0-147.0.3.el8_1.x86_64 kernel-4.18.0-193.el8.x86_64 kernel-4.18.0-193.1.2.el8_2.x86_64 kernel-4.18.0-193.6.3.el8_2.x86_64 kernel-4.18.0-193.13.2.el8_2.x86_64 kernel-4.18.0-193.14.3.el8_2.x86_64 kernel-4.18.0-193.19.1.el8_2.x86_64 181 | **available kernel-core(s):** kernel-core-4.18.0-80.el8.x86_64 kernel-core-4.18.0-80.4.2.el8_0.x86_64 kernel-core-4.18.0-80.1.2.el8_0.x86_64 kernel-core-4.18.0-80.11.2.el8_0.x86_64 kernel-core-4.18.0-147.el8.x86_64 kernel-core-4.18.0-80.11.1.el8_0.x86_64 kernel-core-4.18.0-80.7.2.el8_0.x86_64 kernel-core-4.18.0-80.7.1.el8_0.x86_64 kernel-core-4.18.0-147.8.1.el8_1.x86_64 kernel-core-4.18.0-147.5.1.el8_1.x86_64 kernel-core-4.18.0-147.0.3.el8_1.x86_64 kernel-core-4.18.0-147.3.1.el8_1.x86_64 kernel-core-4.18.0-147.0.2.el8_1.x86_64 kernel-core-4.18.0-193.el8.x86_64 kernel-core-4.18.0-193.1.2.el8_2.x86_64 kernel-core-4.18.0-193.6.3.el8_2.x86_64 kernel-core-4.18.0-193.13.2.el8_2.x86_64 kernel-core-4.18.0-193.14.3.el8_2.x86_64 kernel-core-4.18.0-193.19.1.el8_2.x86_64 182 | **available driver(s):** nvidia-driver-3:455.23.05-1.el8.x86_64 183 | **available kmod(s):** kmod-nvidia-455.23.05-4.18.0-193.19.1-3:455.23.05-2.el8_2.x86_64 184 | 185 | 186 | ## Other NVIDIA driver packages 187 | 188 | ### Precompiled kernel modules 189 | 190 | * [https://github.com/NVIDIA/yum-packaging-precompiled-kmod](https://github.com/NVIDIA/yum-packaging-precompiled-kmod) 191 | 192 | > *note:* more `git` repos with various `.spec` files **coming soon!** 193 | 194 | 195 | ## Contributing 196 | 197 | See [CONTRIBUTING.md](CONTRIBUTING.md) 198 | -------------------------------------------------------------------------------- /nvidia-yum.py: -------------------------------------------------------------------------------- 1 | from yum.plugins import PluginYumExit, TYPE_CORE, TYPE_INTERACTIVE 2 | from yum.packages import YumInstalledPackage 3 | from yum.constants import * 4 | from rpmUtils.miscutils import compareEVR 5 | 6 | import sys 7 | import os 8 | import re 9 | sys.path.insert(0,'/usr/share/yum-cli/') 10 | 11 | import yum 12 | from yum.Errors import * 13 | 14 | from utils import YumUtilBase 15 | from yum import _ 16 | 17 | import logging 18 | import rpmUtils 19 | 20 | requires_api_version = '2.3' 21 | plugin_type = (TYPE_CORE) 22 | 23 | KERNEL_PKG_NAME = 'kernel' 24 | MODULE_PKG_BASENAME = 'kmod-nvidia' 25 | MODULE_PKG_PATTERN = re.compile(MODULE_PKG_BASENAME + '-(branch-[0-9][0-9][0-9]|latest)$') 26 | 27 | DRIVER_PKG_BASENAME = 'nvidia-driver' 28 | DRIVER_PKG_PATTERN = re.compile(DRIVER_PKG_BASENAME + '-(branch-[0-9][0-9][0-9]|latest)$') 29 | DEPEND_ON_KMOD_PATTERNS = [DRIVER_PKG_PATTERN] 30 | 31 | def msg(conduit, message): 32 | conduit.info(1, 'NVIDIA: ' + message) 33 | 34 | def init_hook(conduit): 35 | """This is just here to make sure the plugin was loaded correctly. 36 | Eventually this should just go away.""" 37 | # conduit.info(2, '#### NVIDIA ####') 38 | 39 | def addErase(conduit, tsInfo, package): 40 | """additional sanity check that we only try to addErase() installed packages, 41 | i.e. RPMInstalledPackage instances. If we add others here, e.g. just 42 | YumAvailablePackages, the transaction fails later with a cryptic error message""" 43 | if isinstance(package, YumInstalledPackage): 44 | tsInfo.addErase(package) 45 | else: 46 | conduit.error(2, 'NVIDIA: tried erasing non-installed package ' + str(package) + '/' + str(type(package))) 47 | raise AttributeError 48 | 49 | def get_module_package(conduit, driverPackage, kernelPackage): 50 | """Return the corresponding kernel module package, given an installed driver package 51 | and a kernel package.""" 52 | 53 | tsInfo = conduit.getTsInfo() 54 | modName = get_module_pkg_name(driverPackage) 55 | modRelease = get_module_pkg_release(kernelPackage, driverPackage) 56 | 57 | # We search the DB first so we can be sure to get a YumInstalledPackage 58 | # instance in case the module package is already installed and a 59 | # YumAvailablePackage instance in case it ins't. 60 | db = conduit.getRpmDB() 61 | pkgs = db.searchNevra(modName, driverPackage.epoch, kernelPackage.version, \ 62 | modRelease, driverPackage.arch) 63 | 64 | if pkgs: 65 | # Assume len(pkgs) == 1, but don't assert 66 | return pkgs[0] 67 | 68 | try: 69 | return conduit._base.getPackageObject((modName, driverPackage.arch, 70 | driverPackage.epoch, kernelPackage.version, 71 | modRelease)) 72 | except: 73 | pass 74 | 75 | tsInfo.deselect('kernel') 76 | 77 | return None 78 | 79 | def install_modules_for_kernels(conduit, driverPackage, kernelPackages): 80 | """Install kernel module packages for all given kernel packages""" 81 | tsInfo = conduit.getTsInfo() 82 | db = conduit.getRpmDB() 83 | 84 | newestKernel = get_most_recent_kernel(conduit, kernelPackages) 85 | modPo = get_module_package(conduit, driverPackage, newestKernel) 86 | 87 | if modPo is None: 88 | modName = get_module_pkg_name(driverPackage) 89 | msg(conduit, 'No kernel module package ' + modName + ' for ' + \ 90 | str(newestKernel) + ' and ' + str(driverPackage) + ' found. ' + \ 91 | 'Ignoring the new kernel') 92 | return False 93 | 94 | if db.contains(po = modPo): 95 | return True 96 | 97 | tsInfo.addTrueInstall(modPo) 98 | return True 99 | 100 | def installing_kernels(conduit, kernelPackages, driverPackage): 101 | """When installing new kernels, we need to also install the driver module packages 102 | for each of them.""" 103 | tsInfo = conduit.getTsInfo() 104 | db = conduit.getRpmDB() 105 | 106 | # Remove the kernel module package for all other kernels 107 | newestKernel = get_most_recent_kernel(conduit, kernelPackages) 108 | allKernels = list(kernelPackages) 109 | allKernels.extend(db.returnPackages(patterns=[KERNEL_PKG_NAME])) 110 | 111 | # Will install the kernel module package for the newest one of the kernel packages 112 | success = install_modules_for_kernels(conduit, driverPackage, kernelPackages) 113 | 114 | if not success: 115 | return 116 | 117 | for k in allKernels: 118 | if k != newestKernel: 119 | modPo = get_module_package(conduit, driverPackage, k) 120 | 121 | if db.contains(po = modPo): 122 | addErase(conduit, tsInfo, modPo) 123 | 124 | 125 | def erasing_kernels(conduit, kernelPackages, driverPackage): 126 | """When erasing kernel modules, we want to remove their driver kernel module 127 | packages, provided they are installed at all.""" 128 | db = conduit.getRpmDB() 129 | tsInfo = conduit.getTsInfo() 130 | currentlyInstalledKernels = db.searchNames([KERNEL_PKG_NAME]) 131 | 132 | # This is the list of kernels we will have installed after the given ones were removed. 133 | remainingKernels = list(set(currentlyInstalledKernels) - set(kernelPackages)) 134 | 135 | assert(len(remainingKernels) > 0) 136 | newestRemainingKernel = sorted(remainingKernels, cmp = compare_po, reverse = True)[0] 137 | newestModPo = get_module_package(conduit, driverPackage, newestRemainingKernel) 138 | 139 | # Remove kernel module packages for all the kernels we remove 140 | for k in kernelPackages: 141 | modPo = get_module_package(conduit, driverPackage, k) 142 | 143 | if newestModPo != modPo and db.contains(po = modPo): 144 | addErase(conduit, tsInfo, modPo) 145 | 146 | # Install the kernel module package for the now most recent kernel 147 | if not db.contains(po = newestModPo): 148 | tsInfo.addTrueInstall(newestModPo) 149 | 150 | def erasing_driver(conduit, driverPackage): 151 | """When removing the driver package, we automatically remove all the installed 152 | kernel module packages.""" 153 | db = conduit.getRpmDB() 154 | tsInfo = conduit.getTsInfo() 155 | modPackages = db.returnPackages(patterns=[MODULE_PKG_BASENAME + '*']) 156 | 157 | for modPo in modPackages: 158 | addErase(conduit, tsInfo, modPo) 159 | 160 | def installing_driver(conduit, driverPackage, installingKernels): 161 | """We call this when installing the DRIVER_PKG_BASENAME package. If that happens, 162 | we need to install kernel module packages for all the installed kernels, 163 | as well as the kernels we additionally install in the current transaction""" 164 | db = conduit.getRpmDB() 165 | tsInfo = conduit.getTsInfo() 166 | 167 | install_modules_for_kernels(conduit, driverPackage, []) 168 | 169 | def postresolve_hook(conduit): 170 | db = conduit.getRpmDB() 171 | tsInfo = conduit.getTsInfo() 172 | erasePkgs = tsInfo.getMembersWithState(output_states=[TS_ERASE, TS_UPDATED]) 173 | installPkgs = tsInfo.getMembersWithState(output_states=[TS_INSTALL, TS_TRUEINSTALL, 174 | TS_UPDATE]) 175 | 176 | # Append a '*' to all the package names in our list 177 | installedDriverPackage = db.returnPackages(patterns=[DRIVER_PKG_BASENAME + '*']) 178 | 179 | # The above query for the rpm database returns all packages starting with 180 | # the DRIVER_PKG_BASENAME, but all the subpackages of the nvidia-driver 181 | # package start with 'nvidia-driver', so filter the list out for the correct 182 | # package names. 183 | for k in list(installedDriverPackage): 184 | if not is_driver_po(k): 185 | installedDriverPackage.remove(k) 186 | 187 | installingDriverPackage = None 188 | erasingDriverPackage = None 189 | installingKernels = [] 190 | erasingKernels = [] 191 | 192 | for pkg in installPkgs: 193 | if match_list(DEPEND_ON_KMOD_PATTERNS, pkg.name): 194 | installingDriverPackage = pkg.po 195 | break 196 | 197 | for pkg in erasePkgs: 198 | if match_list(DEPEND_ON_KMOD_PATTERNS, pkg.name): 199 | erasingDriverPackage = pkg.po 200 | break 201 | 202 | for pkg in erasePkgs: 203 | if pkg.po.name == KERNEL_PKG_NAME: 204 | erasingKernels.append(pkg.po) 205 | 206 | for pkg in installPkgs: 207 | if pkg.po.name == KERNEL_PKG_NAME: 208 | installingKernels.append(pkg.po) 209 | 210 | # Since this is a postresolve hook, yum might've already added a kernel module 211 | # package, to satisfy the dependency the nvidia-driver package has. However, 212 | # we will handle that ourselves so remove all of them here. 213 | for member in tsInfo.getMembers(): 214 | if MODULE_PKG_PATTERN.match(member.name): 215 | tsInfo.deselect(member.name) 216 | 217 | if installingDriverPackage: 218 | installing_driver(conduit, installingDriverPackage, list(installingKernels)) 219 | 220 | if erasingDriverPackage: 221 | erasing_driver(conduit, erasingDriverPackage) 222 | 223 | if installedDriverPackage: 224 | if installingKernels and not installingDriverPackage: 225 | installing_kernels(conduit, installingKernels, installedDriverPackage[0]) 226 | 227 | if erasingKernels: 228 | erasing_kernels(conduit, erasingKernels, installedDriverPackage[0]) 229 | 230 | def preresolve_hook(conduit): 231 | tsInfo = conduit.getTsInfo() 232 | moduleUpgrades = filter(lambda m: MODULE_PKG_PATTERN.match(m.name), tsInfo.getMembers()) 233 | 234 | # Not interesting for us 235 | if not moduleUpgrades: 236 | return 237 | 238 | # Stop yum from automatically updating our packages, since we do it ourselves. 239 | # This is technically not necessary, but we need to implement all the 240 | # kmod package update handling ourselves anyway. 241 | 242 | # This should really be the only one 243 | po = moduleUpgrades[0] 244 | tsInfo.deselect(po.name) 245 | 246 | 247 | def match_list(patternList, pkg): 248 | for p in patternList: 249 | if p.match(pkg): 250 | return True; 251 | 252 | return False 253 | 254 | def is_driver_po(po): 255 | return DRIVER_PKG_PATTERN.match(po.name) and 'dkms' not in po.name 256 | 257 | def get_module_pkg_name(driverPackage): 258 | return driverPackage.name.replace(DRIVER_PKG_BASENAME, MODULE_PKG_BASENAME) 259 | 260 | def get_module_pkg_release(kernelPackage, driverPackage): 261 | """In our scheme, we build up the kmod package release field from the 262 | kernel release field as well as the driver version.""" 263 | start = kernelPackage.release[:kernelPackage.release.rfind('.')] 264 | end = kernelPackage.release[kernelPackage.release.rfind('.'):] 265 | 266 | return start + '.r' + driverPackage.version + end 267 | 268 | def compare_po(po1, po2): 269 | return compareEVR((po1.epoch, po1.version, po1.release), 270 | (po2.epoch, po2.version, po2.release)) 271 | 272 | def get_most_recent_kernel(conduit, additional=[]): 273 | db = conduit.getRpmDB() 274 | kernels = list(additional) 275 | kernels.extend(db.returnPackages(patterns=[KERNEL_PKG_NAME])) 276 | 277 | return sorted(kernels, cmp = compare_po, reverse = True)[0] 278 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------