Module: GemChecksums
- Defined in:
- lib/gem_checksums.rb,
lib/gem_checksums/version.rb
Overview
Semantic version for the GemChecksums namespace
Defined Under Namespace
Modules: Version Classes: Error
Constant Summary collapse
- VERSION_REGEX =
Final clause of Regex ‘(?=.gem)` is a positive lookahead assertion See: learnbyexample.github.io/Ruby_Regexp/lookarounds.html#positive-lookarounds Used to pattern match against a gem package name, which always ends with .gem. The positive lookahead ensures it is present, and prevents it from being captured.
/((\d+\.\d+\.\d+)([-.][0-9A-Za-z-]+)*)(?=\.gem)/.freeze
- RUNNING_AS =
File.basename($PROGRAM_NAME)
- BUILD_TIME_ERROR_MESSAGE =
"Environment variable SOURCE_DATE_EPOCH must be set. You'll need to rebuild the gem. See README.md of stone_checksums"- GIT_DRY_RUN_ENV =
ENV.fetch("GEM_CHECKSUMS_GIT_DRY_RUN", "false").casecmp("true") == 0
- CHECKSUMS_DIR =
ENV.fetch("GEM_CHECKSUMS_CHECKSUMS_DIR", "checksums")
- PACKAGE_DIR =
ENV.fetch("GEM_CHECKSUMS_PACKAGE_DIR", "pkg")
- BUILD_TIME_WARNING =
"WARNING: Build time not provided via environment variable SOURCE_DATE_EPOCH.\nWhen using Bundler < 2.7.0, you must set SOURCE_DATE_EPOCH *before* building\nthe gem to ensure consistent SHA-256 & SHA-512 checksums.\n\nPREFERRED: Upgrade to Bundler >= 2.7.0, which uses a constant timestamp for gem builds,\n making SOURCE_DATE_EPOCH unnecessary for reproducible checksums.\n\nIMPORTANT: If you choose to set the build time via SOURCE_DATE_EPOCH,\n you must re-build the gem, i.e. `bundle exec rake build` or `gem build`.\n\nHow to set the build time (only needed for Bundler < 2.7.0):\n\nIn zsh shell:\n- export SOURCE_DATE_EPOCH=$EPOCHSECONDS && echo $SOURCE_DATE_EPOCH\n- If the echo above has no output, then it didn't work.\n- Note that you'll need the `zsh/datetime` module enabled.\n\nIn fish shell:\n- set -x SOURCE_DATE_EPOCH (date +%s)\n- echo $SOURCE_DATE_EPOCH\n\nIn bash shell:\n- export SOURCE_DATE_EPOCH=$(date +%s) && echo $SOURCE_DATE_EPOCH\n\n"
Class Method Summary collapse
-
.generate(git_dry_run: false) ⇒ void
Script, stolen from myself, from github.com/rubygems/guides/pull/325 NOTE (Bundler < 2.7.0): SOURCE_DATE_EPOCH must be set in your environment prior to building the gem.
-
.install_tasks ⇒ void
Make this gem’s rake tasks available in your Rakefile:.
Class Method Details
.generate(git_dry_run: false) ⇒ void
This method returns an undefined value.
Script, stolen from myself, from github.com/rubygems/guides/pull/325 NOTE (Bundler < 2.7.0): SOURCE_DATE_EPOCH must be set in your environment prior to building the gem.
Bundler >= 2.7.0 uses a constant internally, so SOURCE_DATE_EPOCH is no longer required.
This ensures that the gem build, and the gem checksum will use the same ,
and thus will match the SHA-256 checksum generated for every gem on Rubygems.org.
Generate SHA-256 and SHA-512 checksums for a built .gem and commit them.
Behavior regarding reproducible builds depends on Bundler version:
-
Bundler >= 2.7.0: SOURCE_DATE_EPOCH is not required; Bundler uses a constant timestamp.
-
Bundler < 2.7.0: you must set SOURCE_DATE_EPOCH, or upgrade Bundler. If GEM_CHECKSUMS_ASSUME_YES=true is set, the check proceeds non-interactively, but SOURCE_DATE_EPOCH is still required.
The generated checksum files are written to the directory configured via GEM_CHECKSUMS_CHECKSUMS_DIR (default: “checksums”). By default, the newest .gem in GEM_CHECKSUMS_PACKAGE_DIR (default: “pkg”) is used, unless a specific .gem path is passed as the first CLI argument when running under Rake or the gem_checksums CLI.
By default this command will exec a ‘git add && git commit` to include the checksum files. When `git_dry_run` is true, or GEM_CHECKSUMS_GIT_DRY_RUN=true, a dry-run commit is performed, and temporary files are cleaned up.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/gem_checksums.rb', line 89 def generate(git_dry_run: false) git_dry_run_flag = (git_dry_run || GIT_DRY_RUN_ENV) ? "--dry-run" : nil warn("Will run git commit with --dry-run") if git_dry_run_flag # Header: identify the gem and version being run begin puts "[ stone_checksums #{::StoneChecksums::Version::VERSION} ]" rescue StandardError # If for any reason the version constant isn't available, skip header gracefully end # Bundler version gate for reproducibility requirements bundler_ver = Gem::Version.new(Bundler::VERSION) requires_epoch = bundler_ver < Gem::Version.new("2.7.0") if requires_epoch # For older bundler, ask the user whether to proceed, or quit to update. proceed = ENV.fetch("GEM_CHECKSUMS_ASSUME_YES", "").casecmp("true").zero? unless proceed # Non-interactive prompt: advise and abort prompt_msg = "Detected Bundler \#{bundler_ver || \"(unknown)\"} which is older than 2.7.0.\nFor reproducible builds without SOURCE_DATE_EPOCH, please update Bundler to >= 2.7.0.\nIf you still want to proceed with this older Bundler, you must set SOURCE_DATE_EPOCH and re-run.\nTip: set GEM_CHECKSUMS_ASSUME_YES=true to proceed non-interactively (still requires SOURCE_DATE_EPOCH).\n PROMPT\n warn(prompt_msg)\n # Continue to enforce SOURCE_DATE_EPOCH below; if not set, this will raise.\n end\n\n build_time = ENV.fetch(\"SOURCE_DATE_EPOCH\", \"\")\n build_time_missing = !(build_time =~ /\\d{10,}/)\n\n if build_time_missing\n warn(BUILD_TIME_WARNING)\n raise Error, BUILD_TIME_ERROR_MESSAGE\n end\n end\n\n gem_path_parts =\n case RUNNING_AS\n when \"rake\", \"gem_checksums\"\n first_arg = ARGV.first\n first_arg.respond_to?(:split) ? first_arg.split(\"/\") : []\n else # e.g. \"rspec\"\n []\n end\n\n if gem_path_parts.any?\n gem_name = gem_path_parts.last\n gem_pkg = File.join(gem_path_parts)\n puts \"Looking for: \#{gem_pkg.inspect}\"\n gems = Dir[gem_pkg]\n raise Error, \"Unable to find gem \#{gem_pkg}\" if gems.empty?\n\n puts \"Found: \#{gems.inspect}\"\n else\n gem_pkgs = File.join(PACKAGE_DIR, \"*.gem\")\n puts \"Looking for: \#{gem_pkgs.inspect}\"\n gems = Dir[gem_pkgs]\n raise Error, \"Unable to find gems \#{gem_pkgs}\" if gems.empty?\n\n # Sort by newest last\n # [ \"my_gem-2.3.9.gem\", \"my_gem-2.3.11.pre.alpha.4.gem\", \"my_gem-2.3.15.gem\", ... ]\n gems.sort_by! { |gem| Gem::Version.new(gem[VERSION_REGEX]) }\n gem_pkg = gems.last\n gem_path_parts = gem_pkg.split(\"/\")\n gem_name = gem_path_parts.last\n puts \"Found: \#{gems.length} gems; latest is \#{gem_name}\"\n end\n\n pkg_bits = File.read(gem_pkg)\n\n # SHA-512 digest is 8 64-bit words\n digest512_64bit = Digest::SHA512.new.hexdigest(pkg_bits)\n digest512_64bit_path = \"\#{CHECKSUMS_DIR}/\#{gem_name}.sha512\"\n Dir.mkdir(CHECKSUMS_DIR) unless Dir.exist?(CHECKSUMS_DIR)\n File.write(digest512_64bit_path, digest512_64bit)\n\n # SHA-256 digest is 8 32-bit words\n digest256_32bit = Digest::SHA256.new.hexdigest(pkg_bits)\n digest256_32bit_path = \"\#{CHECKSUMS_DIR}/\#{gem_name}.sha256\"\n File.write(digest256_32bit_path, digest256_32bit)\n\n version = gem_name[VERSION_REGEX]\n\n git_cmd = <<-GIT_MSG.rstrip\ngit add \#{CHECKSUMS_DIR}/* && \\\ngit commit \#{git_dry_run_flag} -m \"\u{1F512}\uFE0F Checksums for v\#{version}\"\n GIT_MSG\n\n if git_dry_run_flag\n git_cmd += <<-CLEANUP_MSG\n&& \\\necho \"Cleaning up in dry run mode\" && \\\ngit reset \#{digest512_64bit_path} && \\\ngit reset \#{digest256_32bit_path} && \\\nrm -f \#{digest512_64bit_path} && \\\nrm -f \#{digest256_32bit_path}\n CLEANUP_MSG\n end\n\n puts <<-RESULTS\n[ GEM: \#{gem_name} ]\n[ VERSION: \#{version} ]\n[ GEM PKG LOCATION: \#{gem_pkg} ]\n[ CHECKSUM SHA-256: \#{digest256_32bit} ]\n[ CHECKSUM SHA-512: \#{digest512_64bit} ]\n[ CHECKSUM SHA-256 PATH: \#{digest256_32bit_path} ]\n[ CHECKSUM SHA-512 PATH: \#{digest512_64bit_path} ]\n\n... Running ...\n\n\#{git_cmd}\n RESULTS\n\n if git_dry_run_flag\n %x{\#{git_cmd}}\n else\n # `exec` will replace the current process with the git process, and exit.\n # Within the generate method, Ruby code placed after the `exec` *will not be run*:\n # See: https://www.akshaykhot.com/call-shell-commands-in-ruby\n # But we can't exit the process when testing from RSpec,\n # since that would exit the parent RSpec process\n exec(git_cmd)\n end\nend\n" |
.install_tasks ⇒ void
This method returns an undefined value.
Make this gem’s rake tasks available in your Rakefile:
require "gem_checksums"
60 61 62 |
# File 'lib/gem_checksums.rb', line 60 def install_tasks load("gem_checksums/tasks.rb") end |