diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14c5bb3..0de61c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,19 @@ on: push: jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # No ruby-version input: setup-ruby reads .ruby-version. + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Run RuboCop + run: bundle exec rubocop + test: runs-on: ubuntu-latest steps: diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..2fd7261 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,25 @@ +inherit_gem: + rubocop-shopify: rubocop.yml + +inherit_mode: + merge: + - Exclude + +AllCops: + SuggestExtensions: false + Exclude: + # Polyglot sh/ruby bootstrap scripts (#!/bin/sh preamble re-execing via + # `ruby -x`); not parseable as plain Ruby. + - bin/**/* + - tmp/**/* + +# dev's tests are written in the RSpock dialect: bare comparisons in +# Then/Expect blocks, block-name constants (Given/When/Then/Where), and +# Where-table rows (`a | b`, sometimes with identical operands) are rewritten +# by the AST transform, not evaluated for value. +Lint/Void: + Exclude: + - test/**/* +Lint/BinaryOperatorWithIdenticalOperands: + Exclude: + - test/**/* diff --git a/Gemfile b/Gemfile index 38a2755..7d3e01a 100644 --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,9 @@ gem "rake" gem "pry", "~> 0.14" gem "pry-byebug", "~> 3.11" +# Style +gem "rubocop-shopify", "~> 3.0", require: false + # Sorbet: static + runtime type checking gem "sorbet", group: :development gem "tapioca", require: false, group: [:development, :test] diff --git a/Gemfile.lock b/Gemfile.lock index 66538ed..4060582 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,6 +23,9 @@ GEM diff-lcs (2.0.0) erubi (1.13.1) io-console (0.8.2) + json (2.21.1) + language_server-protocol (3.17.0.6) + lint_roller (1.1.0) logger (1.7.0) method_source (1.1.0) minitest (5.27.0) @@ -47,6 +50,7 @@ GEM byebug (~> 13.0) pry (>= 0.13, < 0.17) racc (1.8.1) + rainbow (3.1.1) rake (13.3.1) rbi (0.3.9) prism (~> 1.0) @@ -55,6 +59,7 @@ GEM logger prism (>= 1.3.0) tsort + regexp_parser (2.12.0) reline (0.6.3) io-console (~> 0.5) require-hooks (0.2.3) @@ -65,6 +70,23 @@ GEM mocha (>= 1.0) parser (>= 3.0) unparser (>= 0.6) + rubocop (1.88.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (>= 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.50.0) + parser (>= 3.3.7.2) + prism (~> 1.7) + rubocop-shopify (3.0.1) + lint_roller + rubocop (~> 1.72, >= 1.72.1) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) sorbet (0.6.12971) @@ -97,6 +119,9 @@ GEM yard-sorbet thor (1.5.0) tsort (0.2.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) unparser (0.8.2) diff-lcs (>= 1.6, < 3) parser (>= 3.3.0) @@ -120,6 +145,7 @@ DEPENDENCIES rake rbs (~> 4.0.0.dev.5) rspock (~> 2.3) + rubocop-shopify (~> 3.0) sorbet tapioca @@ -136,6 +162,9 @@ CHECKSUMS diff-lcs (2.0.0) sha256=708a5d52ec2945b50f8f53a181174aa1ef2c496edf81c05957fe956dabb363d5 erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9 io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc + json (2.21.1) sha256=13a43df75d95641443f5702dff350f237164a9d811ff0f2c2800d4d980220583 + language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0 + lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5 minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5 @@ -148,13 +177,18 @@ CHECKSUMS pry (0.16.0) sha256=d76c69065698ed1f85e717bd33d7942c38a50868f6b0673c636192b3d1b6054e pry-byebug (3.12.0) sha256=594e094ae8a8390a7ad4c7b36ae36e13304ed02664c67417d108dc5f7213d1b7 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f + rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c rbi (0.3.9) sha256=d6a187bd0b376e999d3d82a5e5798a61178be98b894b7b35741c14162c9ea015 rbs (4.0.0.dev.5) sha256=273938e5a9c7f06b041be583588f2aa1976149bda2ce213e33e129c51534e174 + regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 require-hooks (0.2.3) sha256=224be5b4be0fd2a47cb73286c500da366704a54ec195b6627366380c950efac8 rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 rspock (2.5.0) sha256=5bac4d22eab6f10a04523d9d5368f451b4c261c51cab092d8a8e868318f9b348 + rubocop (1.88.2) sha256=8def251c90cd955feb4daa3edc0ab56893250c4ce90ef81e6c80c03f9a939bbf + rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db + rubocop-shopify (3.0.1) sha256=4adffa6313294bd9da2b0896ae44c5eb8e419336b2413de20c38b7691a7e6774 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef sorbet (0.6.12971) sha256=2b22b9dc976ac4f3d5af27e0365c9b0235fca0244ec644f4f52f9e7bb6b67acc @@ -167,6 +201,8 @@ CHECKSUMS tapioca (0.17.10) sha256=880a682ca8314f798dd09e9f104134fbf1a713c13be51f7dd4741dd434e6471b thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f + unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 + unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f unparser (0.8.2) sha256=654af48a6cfa25baf145563e01f40c60ca7d5c7520ff506912d0bb2f1240fd00 yard (0.9.38) sha256=721fb82afb10532aa49860655f6cc2eaa7130889df291b052e1e6b268283010f yard-sorbet (0.9.0) sha256=03d1aa461b9e9c82b886919a13aa3e09fcf4d1852239d2967ed97e92723ffe21 diff --git a/dev.yml b/dev.yml index c166abf..e67c582 100644 --- a/dev.yml +++ b/dev.yml @@ -12,6 +12,9 @@ commands: test: desc: Run this repo's tests run: ./bin/test.rb + style: + desc: Run RuboCop + run: bundle exec rubocop release: desc: Release a new version (bump, tag, push, Homebrew formula) run: ./bin/release.rb diff --git a/lib/dev/deps.rb b/lib/dev/deps.rb index faba00f..6c11083 100644 --- a/lib/dev/deps.rb +++ b/lib/dev/deps.rb @@ -10,48 +10,48 @@ module Dev module Deps @last_config = nil - def self.define(&block) - @last_config = Config.define(&block) - end + class << self + def define(&block) + @last_config = Config.define(&block) + end - # The most recently defined config (from the last call to .define). - # Useful for retrieving the config after loading a dependencies.rb file. - # - # @return [Config, nil] - def self.last_config - @last_config - end + # The most recently defined config (from the last call to .define). + # Useful for retrieving the config after loading a dependencies.rb file. + # + # @return [Config, nil] + attr_reader :last_config - # Clears the last defined config. Call before loading a dependencies.rb: - # a file that never calls .define (e.g. dev's own bootstrap-constants - # dependencies.rb) would otherwise leave a previously loaded project's - # config visible as if it were its own. - def self.reset! - @last_config = nil - end + # Clears the last defined config. Call before loading a dependencies.rb: + # a file that never calls .define (e.g. dev's own bootstrap-constants + # dependencies.rb) would otherwise leave a previously loaded project's + # config visible as if it were its own. + def reset! + @last_config = nil + end - # Detect the current environment (ci vs dev) from the CI variable alone. - # - # Deliberately NOT platform-based: a Linux workstation is "dev" and a Mac - # CI runner is "ci". The one caller that needed the old Linux-implies-CI - # clause (bin/install-build-deps.rb, which runs inside docker builds where - # no CI variable exists) now declares env: "ci" explicitly instead of - # detecting it — fix by declaration, not detection. - # - # @return [String] "ci" or "dev" - def self.detect_env - ENV["CI"].to_s =~ /\A(true|1)\z/i ? "ci" : "dev" - end + # Detect the current environment (ci vs dev) from the CI variable alone. + # + # Deliberately NOT platform-based: a Linux workstation is "dev" and a Mac + # CI runner is "ci". The one caller that needed the old Linux-implies-CI + # clause (bin/install-build-deps.rb, which runs inside docker builds where + # no CI variable exists) now declares env: "ci" explicitly instead of + # detecting it — fix by declaration, not detection. + # + # @return [String] "ci" or "dev" + def detect_env + ENV["CI"].to_s =~ /\A(true|1)\z/i ? "ci" : "dev" + end - # Detect the host OS for host-gated dependency filtering (the `host:` - # declaration axis). Matches the symbols the DSL accepts (:darwin, :linux). - # - # @return [String] "darwin", "linux", or "windows" - def self.detect_host - case RUBY_PLATFORM - when /darwin/ then "darwin" - when /linux/ then "linux" - else "windows" + # Detect the host OS for host-gated dependency filtering (the `host:` + # declaration axis). Matches the symbols the DSL accepts (:darwin, :linux). + # + # @return [String] "darwin", "linux", or "windows" + def detect_host + case RUBY_PLATFORM + when /darwin/ then "darwin" + when /linux/ then "linux" + else "windows" + end end end end diff --git a/lib/dev/deps/accessor.rb b/lib/dev/deps/accessor.rb index ff95ba9..0ed736f 100644 --- a/lib/dev/deps/accessor.rb +++ b/lib/dev/deps/accessor.rb @@ -77,7 +77,7 @@ def ficsit_path(name, platform) ) unless @cache.exists?(key) raise NotCachedError, - "#{name} (#{platform}) is not cached — run dev up to download it" + "#{name} (#{platform}) is not cached — run dev up to download it" end @cache.path(key) @@ -92,8 +92,8 @@ def xcode_developer_dir developer_dir = Pathname(XcodeIntegration.developer_dir(dep.version, root: @xcode_install_root)) unless developer_dir.directory? raise NotInstalledError, - "xcode #{dep.version} is not installed at " \ - "#{XcodeIntegration.app_path(dep.version, root: @xcode_install_root)} — run dev up" + "xcode #{dep.version} is not installed at " \ + "#{XcodeIntegration.app_path(dep.version, root: @xcode_install_root)} — run dev up" end developer_dir @@ -118,7 +118,7 @@ def locked_platform(dep, platform) unless target available = platforms.keys.join(", ") raise PlatformNotLockedError, - "#{dep.name} has no locked #{platform} platform (locked: #{available})" + "#{dep.name} has no locked #{platform} platform (locked: #{available})" end target diff --git a/lib/dev/deps/bundler_repository.rb b/lib/dev/deps/bundler_repository.rb index 8e05574..f2d644e 100644 --- a/lib/dev/deps/bundler_repository.rb +++ b/lib/dev/deps/bundler_repository.rb @@ -62,7 +62,7 @@ def fetch(id) name = id["name"] pin = pins.fetch(name) do raise MissingGemError, - "gem #{name.inspect} is not in #{LOCKFILE} — run `dev update-deps`" + "gem #{name.inspect} is not in #{LOCKFILE} — run `dev update-deps`" end Dependency.new( diff --git a/lib/dev/deps/cli_ui.rb b/lib/dev/deps/cli_ui.rb index 839e299..2d3fd6a 100644 --- a/lib/dev/deps/cli_ui.rb +++ b/lib/dev/deps/cli_ui.rb @@ -8,62 +8,64 @@ module Deps # that needs progress/status output (integrations, fetcher, orchestrator) # should call through this module rather than coupling to CLI::UI directly. module CliUI - def self.available? - return @available if defined?(@available) + class << self + def available? + return @available if defined?(@available) - @available = begin - require "cli/ui" - true - rescue LoadError - false + @available = begin + require "cli/ui" + true + rescue LoadError + false + end end - end - # Print a success status line. - # - # @param name [String] label to display - def self.step_ok(name) - if available? - CLI::UI.puts("#{CLI::UI::Glyph::CHECK} #{name}") - else - puts " ok: #{name}" + # Print a success status line. + # + # @param name [String] label to display + def step_ok(name) + if available? + CLI::UI.puts("#{CLI::UI::Glyph::CHECK} #{name}") + else + puts " ok: #{name}" + end end - end - # Print a failure status line. - # - # @param name [String] label to display - def self.step_fail(name) - if available? - CLI::UI.puts("#{CLI::UI::Glyph::X} #{name}") - else - puts " FAIL: #{name}" + # Print a failure status line. + # + # @param name [String] label to display + def step_fail(name) + if available? + CLI::UI.puts("#{CLI::UI::Glyph::X} #{name}") + else + puts " FAIL: #{name}" + end end - end - # Run a block with a spinner (or plain-text fallback). - # - # @param title [String] spinner label - # @yield block to execute during spinner - def self.with_spinner(title, &block) - if available? - CLI::UI::Spinner.spin(title, &block) - else - puts " #{title}..." - block.call + # Run a block with a spinner (or plain-text fallback). + # + # @param title [String] spinner label + # @yield block to execute during spinner + def with_spinner(title, &block) + if available? + CLI::UI::Spinner.spin(title, &block) + else + puts " #{title}..." + block.call + end end - end - # Sanitize a string to valid UTF-8. - # - # @param str [String, nil] input string - # @return [String, nil] UTF-8 safe string - def self.sanitize_utf8(str) - return str if str.nil? || (str.encoding == Encoding::UTF_8 && str.valid_encoding?) + # Sanitize a string to valid UTF-8. + # + # @param str [String, nil] input string + # @return [String, nil] UTF-8 safe string + def sanitize_utf8(str) + return str if str.nil? || (str.encoding == Encoding::UTF_8 && str.valid_encoding?) - str.dup.force_encoding(Encoding::UTF_8).encode( - Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "?", - ) + str.dup.force_encoding(Encoding::UTF_8).encode( + Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "?", + ) + end end end end diff --git a/lib/dev/deps/cmake_integration.rb b/lib/dev/deps/cmake_integration.rb index 6faff4b..b73f7e4 100644 --- a/lib/dev/deps/cmake_integration.rb +++ b/lib/dev/deps/cmake_integration.rb @@ -100,7 +100,7 @@ def fetch_git(dep, dest) system("git", "clone", "--no-checkout", "-q", dep.metadata["repo"], dest.to_s) || raise(GitCloneError, "git clone #{dep.metadata["repo"]} failed") system("git", "-c", "advice.detachedHead=false", "checkout", dep.version, - chdir: dest.to_s) || + chdir: dest.to_s) || raise(GitCheckoutError, "git checkout #{dep.version} failed") end diff --git a/lib/dev/deps/config.rb b/lib/dev/deps/config.rb index 58a0188..9e2df5b 100644 --- a/lib/dev/deps/config.rb +++ b/lib/dev/deps/config.rb @@ -8,7 +8,7 @@ module Deps # Parsed dependency configuration. Returned by Dev::Deps.define. class Config attr_reader :taps, :groups, :declarations, :ruby_version_requirement, - :lua_version, :python_version, :registered_integrations + :lua_version, :python_version, :registered_integrations # @param taps [Array] declared Homebrew taps # @param groups [Hash] group name → { "brew" => [...], "env" => {...} } @@ -37,27 +37,29 @@ def group(name) @groups[name.to_s] || { "brew" => [], "env" => {} } end - # Evaluate a DSL block and return a Config instance. - # - # @param block [Proc] DSL block evaluated in DSL context - # @return [Config] - def self.define(&block) - dsl = DSL.new - dsl.instance_eval(&block) if block + class << self + # Evaluate a DSL block and return a Config instance. + # + # @param block [Proc] DSL block evaluated in DSL context + # @return [Config] + def define(&block) + dsl = DSL.new + dsl.instance_eval(&block) if block - taps = dsl.taps.map do |_name, raw| - Tap.new(name: raw["name"], url: raw["url"]) - end + taps = dsl.taps.map do |_name, raw| + Tap.new(name: raw["name"], url: raw["url"]) + end - new( - taps:, - groups: dsl.groups, - declarations: dsl.declarations, - ruby_version_requirement: dsl.ruby_version_requirement, - lua_version: dsl.lua_version_value, - python_version: dsl.python_version_value, - registered_integrations: dsl.registered_integrations, - ) + new( + taps:, + groups: dsl.groups, + declarations: dsl.declarations, + ruby_version_requirement: dsl.ruby_version_requirement, + lua_version: dsl.lua_version_value, + python_version: dsl.python_version_value, + registered_integrations: dsl.registered_integrations, + ) + end end end end diff --git a/lib/dev/deps/dependency.rb b/lib/dev/deps/dependency.rb index b9cbb1c..0aa3725 100644 --- a/lib/dev/deps/dependency.rb +++ b/lib/dev/deps/dependency.rb @@ -14,7 +14,10 @@ module Deps # - dependencies: transitive dependencies discovered during fetch ([{name:, constraint:}, …]) # - post_install: callable or array of callables to run after the dep is fetched. # Each callable receives (dep, project_root). Not serialized to lockfile. - Dependency = Data.define(:name, :integration, :group, :version, :hash, :metadata, :dependencies, :post_install) do + # The :hash member (integrity hash) shadows Data#hash; instances are never + # used as Hash keys, and renaming it would ripple through the lockfile + # format. Known trade-off. + Dependency = Data.define(:name, :integration, :group, :version, :hash, :metadata, :dependencies, :post_install) do # rubocop:disable Lint/DataDefineOverride def initialize(name:, integration:, group:, version:, hash:, metadata:, dependencies: [], post_install: nil) super(name:, integration:, group:, version:, hash:, metadata:, dependencies:, post_install:) end diff --git a/lib/dev/deps/dsl.rb b/lib/dev/deps/dsl.rb index ddbb04c..d30589d 100644 --- a/lib/dev/deps/dsl.rb +++ b/lib/dev/deps/dsl.rb @@ -11,7 +11,7 @@ class DSL DEFAULT_GEM_GROUP = :app attr_reader :taps, :groups, :declarations, :ruby_version_requirement, - :lua_version_value, :python_version_value, :registered_integrations, :registered_methods + :lua_version_value, :python_version_value, :registered_integrations, :registered_methods def initialize @taps = {} @@ -262,8 +262,8 @@ def gh(name_or_slug, tag:, install_dir:, github: nil, repo: nil, assets: nil, bu unless [assets, build].compact.size == 1 raise ArgumentError, - "gh #{name.inspect}: provide exactly one of assets: (prebuilt release asset) " \ - "or build: (build from source)" + "gh #{name.inspect}: provide exactly one of assets: (prebuilt release asset) " \ + "or build: (build from source)" end spec = spec.merge(repo: slug, tag: tag, install_dir: install_dir) diff --git a/lib/dev/deps/fetcher.rb b/lib/dev/deps/fetcher.rb index d194087..910b616 100644 --- a/lib/dev/deps/fetcher.rb +++ b/lib/dev/deps/fetcher.rb @@ -10,109 +10,109 @@ module Dev module Deps # Fetches runtime dependencies (git repos and URL tarballs) into the build directory. module Fetcher - # Fetch all runtime deps that are missing from disk. - # Returns an array of [FETCHCONTENT_SOURCE_DIR_, path] pairs for cmake. - def self.fetch_missing(root:, build_dir: "build", verbose: false) - lock_path = File.join(root, "deps.lock.cmake") - deps_dir = File.join(root, build_dir, "_deps") - runtime_deps = Lockfile.parse(lock_path) - runtime_ref_by_name = Lockfile.runtime_ref_map - out_of_sync = Lockfile.check_sync!(root: root) + class << self + # Fetch all runtime deps that are missing from disk. + # Returns an array of [FETCHCONTENT_SOURCE_DIR_, path] pairs for cmake. + def fetch_missing(root:, build_dir: "build", verbose: false) + lock_path = File.join(root, "deps.lock.cmake") + deps_dir = File.join(root, build_dir, "_deps") + runtime_deps = Lockfile.parse(lock_path) + runtime_ref_by_name = Lockfile.runtime_ref_map + out_of_sync = Lockfile.check_sync!(root: root) - fetchcontent_source_dirs = [] + fetchcontent_source_dirs = [] - runtime_deps.each do |dep| - name = dep[:name] - ref_display = runtime_ref_by_name[name] || (dep[:sha] ? dep[:sha][0, 7] : "tarball") - label = "#{name}@#{ref_display}" - dep_src = File.join(deps_dir, "#{name}-src") - populated = populated?(dep_src, dep) - in_sync = !out_of_sync.include?(name) + runtime_deps.each do |dep| + name = dep[:name] + ref_display = runtime_ref_by_name[name] || (dep[:sha] ? dep[:sha][0, 7] : "tarball") + label = "#{name}@#{ref_display}" + dep_src = File.join(deps_dir, "#{name}-src") + populated = populated?(dep_src, dep) + in_sync = !out_of_sync.include?(name) - if in_sync - if populated - CliUI.step_ok(label) - fetchcontent_source_dirs << ["FETCHCONTENT_SOURCE_DIR_#{name.upcase}", File.expand_path(dep_src)] + if in_sync + if populated + CliUI.step_ok(label) + fetchcontent_source_dirs << ["FETCHCONTENT_SOURCE_DIR_#{name.upcase}", File.expand_path(dep_src)] + else + fetch_dep(dep, dep_src, lock_path, label: label, verbose: verbose) + fetchcontent_source_dirs << ["FETCHCONTENT_SOURCE_DIR_#{name.upcase}", File.expand_path(dep_src)] + end else - fetch_dep(dep, dep_src, lock_path, label: label, verbose: verbose) - fetchcontent_source_dirs << ["FETCHCONTENT_SOURCE_DIR_#{name.upcase}", File.expand_path(dep_src)] + CliUI.step_fail(label) + current_content = File.read(lock_path) + generated = Lockfile.dep_pin(current_content, name) + if CliUI.available? + CLI::UI.puts(CLI::UI.fmt("{{yellow:lockfile out of sync for #{name}: #{generated}}}")) + else + puts " WARNING: lockfile out of sync for #{name}: #{generated}" + end end - else - CliUI.step_fail(label) - current_content = File.read(lock_path) - generated = Lockfile.dep_pin(current_content, name) + end + + if out_of_sync.any? + puts "" + msg = "Run update-deps and commit deps.lock.cmake." if CliUI.available? - CLI::UI.puts(CLI::UI.fmt("{{yellow:lockfile out of sync for #{name}: #{generated}}}")) + CLI::UI.puts(CLI::UI.fmt("{{yellow:#{msg}}}")) else - puts " WARNING: lockfile out of sync for #{name}: #{generated}" + puts msg end + exit 1 end + + cleanup_stale(deps_dir, runtime_deps) + fetchcontent_source_dirs end - if out_of_sync.any? - puts "" - msg = "Run update-deps and commit deps.lock.cmake." - if CliUI.available? - CLI::UI.puts(CLI::UI.fmt("{{yellow:#{msg}}}")) + def fetch_git(repo, sha, dest_dir, quiet: false) + FileUtils.mkdir_p(File.dirname(dest_dir)) + FileUtils.rm_rf(dest_dir) if File.exist?(dest_dir) + if quiet + _out, err, status = Open3.capture3("git", "clone", "--no-checkout", "-q", repo, dest_dir) + abort("git clone #{repo} failed: #{err}") unless status.success? + env = { "GIT_TERMINAL_PROMPT" => "0" } + _out2, err2, status2 = Open3.capture3(env, "git", "-c", "advice.detachedHead=false", "checkout", sha, chdir: dest_dir) + abort("git checkout #{sha} failed: #{err2}") unless status2.success? else - puts msg + system("git", "clone", "--no-checkout", repo, dest_dir) || abort("git clone #{repo} failed") + system("git", "checkout", sha, chdir: dest_dir) || abort("git checkout #{sha} failed") end - exit 1 - end - - cleanup_stale(deps_dir, runtime_deps) - fetchcontent_source_dirs - end - - def self.fetch_git(repo, sha, dest_dir, quiet: false) - FileUtils.mkdir_p(File.dirname(dest_dir)) - FileUtils.rm_rf(dest_dir) if File.exist?(dest_dir) - if quiet - _out, err, status = Open3.capture3("git", "clone", "--no-checkout", "-q", repo, dest_dir) - abort("git clone #{repo} failed: #{err}") unless status.success? - env = { "GIT_TERMINAL_PROMPT" => "0" } - _out2, err2, status2 = Open3.capture3(env, "git", "-c", "advice.detachedHead=false", "checkout", sha, chdir: dest_dir) - abort("git checkout #{sha} failed: #{err2}") unless status2.success? - else - system("git", "clone", "--no-checkout", repo, dest_dir) || abort("git clone #{repo} failed") - system("git", "checkout", sha, chdir: dest_dir) || abort("git checkout #{sha} failed") end - end - # Downloads and extracts a tarball, returns the SHA256 hex digest. - def self.fetch_tarball(url, dest_dir, quiet: false) - FileUtils.mkdir_p(File.dirname(dest_dir)) - FileUtils.rm_rf(dest_dir) if File.exist?(dest_dir) - sha256_hex = nil - Dir.mktmpdir("dev-deps-dep-") do |tmpdir| - tarball = File.join(tmpdir, File.basename(URI(url).path)) - curl_args = ["-fSL", "-o", tarball, url] - curl_args.unshift("-s") if quiet - _out, err, status = Open3.capture3("curl", *curl_args) - abort("download #{url} failed: #{err}") unless status.success? - sha256_hex = Digest::SHA256.file(tarball).hexdigest - _out, err, status = Open3.capture3("tar", "xzf", tarball, "-C", tmpdir) - abort("tar extract failed: #{err}") unless status.success? - inner = Dir.entries(tmpdir).find { |e| e != "." && e != ".." && File.directory?(File.join(tmpdir, e)) } - abort("tarball had no top-level directory") unless inner - FileUtils.mv(File.join(tmpdir, inner), dest_dir) + # Downloads and extracts a tarball, returns the SHA256 hex digest. + def fetch_tarball(url, dest_dir, quiet: false) + FileUtils.mkdir_p(File.dirname(dest_dir)) + FileUtils.rm_rf(dest_dir) if File.exist?(dest_dir) + sha256_hex = nil + Dir.mktmpdir("dev-deps-dep-") do |tmpdir| + tarball = File.join(tmpdir, File.basename(URI(url).path)) + curl_args = ["-fSL", "-o", tarball, url] + curl_args.unshift("-s") if quiet + _out, err, status = Open3.capture3("curl", *curl_args) + abort("download #{url} failed: #{err}") unless status.success? + sha256_hex = Digest::SHA256.file(tarball).hexdigest + _out, err, status = Open3.capture3("tar", "xzf", tarball, "-C", tmpdir) + abort("tar extract failed: #{err}") unless status.success? + inner = Dir.entries(tmpdir).find { |e| e != "." && e != ".." && File.directory?(File.join(tmpdir, e)) } + abort("tarball had no top-level directory") unless inner + FileUtils.mv(File.join(tmpdir, inner), dest_dir) + end + sha256_hex end - sha256_hex - end - # Sets or updates dep__hash in the lockfile. - def self.update_lockfile_hash(lock_path, name, sha256_hex) - content = File.read(lock_path) - new_line = "set(dep_#{name}_hash \"SHA256=#{sha256_hex}\")\n" - if content.include?("dep_#{name}_hash") - content.sub!(/set\(dep_#{Regexp.escape(name)}_hash\s+"[^"]*"\)\n?/, new_line) - else - content.sub!(/(set\(dep_#{Regexp.escape(name)}_url "[^"]*"\)\n)/, "\\1#{new_line}") + # Sets or updates dep__hash in the lockfile. + def update_lockfile_hash(lock_path, name, sha256_hex) + content = File.read(lock_path) + new_line = "set(dep_#{name}_hash \"SHA256=#{sha256_hex}\")\n" + if content.include?("dep_#{name}_hash") + content.sub!(/set\(dep_#{Regexp.escape(name)}_hash\s+"[^"]*"\)\n?/, new_line) + else + content.sub!(/(set\(dep_#{Regexp.escape(name)}_url "[^"]*"\)\n)/, "\\1#{new_line}") + end + File.write(lock_path, content) end - File.write(lock_path, content) - end - class << self private def populated?(dep_src, dep) diff --git a/lib/dev/deps/ficsit_integration.rb b/lib/dev/deps/ficsit_integration.rb index 4d74211..9862b39 100644 --- a/lib/dev/deps/ficsit_integration.rb +++ b/lib/dev/deps/ficsit_integration.rb @@ -24,24 +24,26 @@ class MissingPlatformsError < StandardError; end class DownloadError < StandardError; end class IntegrityError < StandardError; end - # Build the content-cache key for a locked mod platform. Shared with the - # `dev deps path` accessor so consumers never reconstruct the key by hand. - # - # @param name [String] mod reference (e.g. "SML") - # @param version [String] locked version (e.g. "3.12.0") - # @param platform [String] ficsit target name (e.g. "LinuxServer") - # @param hash [String] locked integrity hash ("SHA256=…") - # @return [String] cache key, e.g. "ficsit/SML-3.12.0-LinuxServer-.zip" - def self.cache_key(name:, version:, platform:, hash:) - "ficsit/#{name}-#{version}-#{platform}-#{strip_algo(hash)}.zip" - end + class << self + # Build the content-cache key for a locked mod platform. Shared with the + # `dev deps path` accessor so consumers never reconstruct the key by hand. + # + # @param name [String] mod reference (e.g. "SML") + # @param version [String] locked version (e.g. "3.12.0") + # @param platform [String] ficsit target name (e.g. "LinuxServer") + # @param hash [String] locked integrity hash ("SHA256=…") + # @return [String] cache key, e.g. "ficsit/SML-3.12.0-LinuxServer-.zip" + def cache_key(name:, version:, platform:, hash:) + "ficsit/#{name}-#{version}-#{platform}-#{strip_algo(hash)}.zip" + end - # Strip the "SHA256=" algorithm prefix from a locked hash. - # - # @param hash [String, nil] - # @return [String] - def self.strip_algo(hash) - hash.to_s.sub(/\ASHA256=/, "") + # Strip the "SHA256=" algorithm prefix from a locked hash. + # + # @param hash [String, nil] + # @return [String] + def strip_algo(hash) + hash.to_s.sub(/\ASHA256=/, "") + end end # Download and cache every locked platform zip for each ficsit dep. @@ -59,8 +61,8 @@ def install(dep) platforms = dep.metadata["platforms"] if platforms.nil? || platforms.empty? raise MissingPlatformsError, - "#{dep.name}@#{dep.version} has no resolved platforms — declare it in a " \ - "group with a platform and run dev update-deps" + "#{dep.name}@#{dep.version} has no resolved platforms — declare it in a " \ + "group with a platform and run dev update-deps" end platforms.each { |platform, target| install_platform(dep, platform, target) } @@ -107,7 +109,7 @@ def verify(path, expected, dep, platform) return if actual == sha raise IntegrityError, - "SHA256 mismatch for #{dep.name}@#{dep.version} (#{platform}): expected #{sha}, got #{actual}" + "SHA256 mismatch for #{dep.name}@#{dep.version} (#{platform}): expected #{sha}, got #{actual}" end end end diff --git a/lib/dev/deps/ficsit_repository.rb b/lib/dev/deps/ficsit_repository.rb index f98636e..640d750 100644 --- a/lib/dev/deps/ficsit_repository.rb +++ b/lib/dev/deps/ficsit_repository.rb @@ -125,7 +125,7 @@ def resolve_platforms(mod_reference, version_data, requested) unless target_data available = targets.map { |t| t["targetName"] }.join(", ") raise TargetNotFoundError, - "#{mod_reference} #{version_data["version"]} has no #{target_name} target (available: #{available})" + "#{mod_reference} #{version_data["version"]} has no #{target_name} target (available: #{available})" end acc[target_name] = { diff --git a/lib/dev/deps/gh_integration.rb b/lib/dev/deps/gh_integration.rb index 94e2709..22e045a 100644 --- a/lib/dev/deps/gh_integration.rb +++ b/lib/dev/deps/gh_integration.rb @@ -179,7 +179,7 @@ def download_source(dep, archive_path) return if success raise DownloadError, - "gh api tarball failed for #{dep.metadata["repo"]}@#{dep.version}" + "gh api tarball failed for #{dep.metadata["repo"]}@#{dep.version}" end # Extract a GitHub source tarball into source_dir, stripping the single @@ -233,7 +233,7 @@ def run_build(dep, source_dir, install_dir) return if success raise BuildError, - "build failed for #{dep.name}@#{dep.version} (#{dep.metadata["build"]})" + "build failed for #{dep.name}@#{dep.version} (#{dep.metadata["build"]})" end # Resolve a build: value to a command. A project-relative path to an existing @@ -265,7 +265,7 @@ def download_assets(dep, archives_dir) return if success raise DownloadError, - "gh release download failed for #{dep.metadata["repo"]}@#{dep.version}" + "gh release download failed for #{dep.metadata["repo"]}@#{dep.version}" end # Verify downloaded files against the digests locked at resolve time. @@ -287,7 +287,7 @@ def verify_assets(dep, archives_dir) next if actual == expected raise IntegrityError, - "SHA256 mismatch for #{asset["name"]}: expected #{expected}, got #{actual}" + "SHA256 mismatch for #{asset["name"]}: expected #{expected}, got #{actual}" end end @@ -324,7 +324,7 @@ def archive_base_name(path) def extract_zstd_tarball(base_name, parts, dest_dir) unless base_name.end_with?(".tar.zst") raise UnsupportedArchiveError, - "unsupported archive #{base_name} — only .tar.zst[.NN] is supported" + "unsupported archive #{base_name} — only .tar.zst[.NN] is supported" end pipeline = "cat #{parts.map(&:to_s).shelljoin} | zstd -d | " \ diff --git a/lib/dev/deps/gh_repository.rb b/lib/dev/deps/gh_repository.rb index 6171dfa..07a359e 100644 --- a/lib/dev/deps/gh_repository.rb +++ b/lib/dev/deps/gh_repository.rb @@ -58,7 +58,7 @@ def fetch_prebuilt(id) assets = matching_assets(release, pattern) if assets.empty? raise NoMatchingAssetsError, - "no assets matching #{pattern.inspect} in #{repo_slug}@#{tag}" + "no assets matching #{pattern.inspect} in #{repo_slug}@#{tag}" end Dependency.new( diff --git a/lib/dev/deps/hooks/unreal_module.rb b/lib/dev/deps/hooks/unreal_module.rb index ed6205e..c1ea0cd 100644 --- a/lib/dev/deps/hooks/unreal_module.rb +++ b/lib/dev/deps/hooks/unreal_module.rb @@ -11,50 +11,52 @@ module Hooks # tag: "v1.17.0", targets: ["gtest", "gmock"], # post_install: Dev::Deps::Hooks::UnrealModule module UnrealModule - # @param dep [Dependency] the resolved dependency - # @param root [Pathname] project root - def self.call(dep, root) - src_dir = root / "build" / "_deps" / "#{dep.name}-src" - return unless src_dir.directory? + class << self + # @param dep [Dependency] the resolved dependency + # @param root [Pathname] project root + def call(dep, root) + src_dir = root / "build" / "_deps" / "#{dep.name}-src" + return unless src_dir.directory? - module_name = to_module_name(dep.name) - build_cs = src_dir / "#{module_name}.Build.cs" - return if build_cs.exist? + module_name = to_module_name(dep.name) + build_cs = src_dir / "#{module_name}.Build.cs" + return if build_cs.exist? - File.write(build_cs, generate_build_cs(module_name, dep)) - end + File.write(build_cs, generate_build_cs(module_name, dep)) + end - # Convert a dependency name like "googletest" to a PascalCase UE module name. - # - # @param name [String] - # @return [String] - def self.to_module_name(name) - name.gsub(/[^a-zA-Z0-9]/, " ").split.map(&:capitalize).join - end + # Convert a dependency name like "googletest" to a PascalCase UE module name. + # + # @param name [String] + # @return [String] + def to_module_name(name) + name.gsub(/[^a-zA-Z0-9]/, " ").split.map(&:capitalize).join + end - # Generate a .Build.cs that exposes the dep's source as a UE module. - # - # @param module_name [String] PascalCase module name - # @param dep [Dependency] - # @return [String] - def self.generate_build_cs(module_name, dep) - public_includes = dep.metadata["public_includes"] || ["."] - includes_lines = public_includes.map { |inc| " \"#{inc}\"" }.join(",\n") + # Generate a .Build.cs that exposes the dep's source as a UE module. + # + # @param module_name [String] PascalCase module name + # @param dep [Dependency] + # @return [String] + def generate_build_cs(module_name, dep) + public_includes = dep.metadata["public_includes"] || ["."] + includes_lines = public_includes.map { |inc| " \"#{inc}\"" }.join(",\n") - <<~CS - using UnrealBuildTool; + <<~CS + using UnrealBuildTool; - public class #{module_name} : ModuleRules - { - public #{module_name}(ReadOnlyTargetRules Target) : base(Target) - { - Type = ModuleType.External; - PublicIncludePaths.AddRange(new string[] { - #{includes_lines} - }); - } - } - CS + public class #{module_name} : ModuleRules + { + public #{module_name}(ReadOnlyTargetRules Target) : base(Target) + { + Type = ModuleType.External; + PublicIncludePaths.AddRange(new string[] { + #{includes_lines} + }); + } + } + CS + end end end end diff --git a/lib/dev/deps/registry.rb b/lib/dev/deps/registry.rb index 3e98cad..f0c89d6 100644 --- a/lib/dev/deps/registry.rb +++ b/lib/dev/deps/registry.rb @@ -132,50 +132,50 @@ def host? ), ].freeze - # Build the integration-type -> Repository hash the Resolver consumes. - # - # @param project_root [Pathname] project root (threaded to repositories that need it) - # @param ruby_version_requirement [String, nil] for the bundler-generated Gemfile - # @return [Hash{Symbol => Repository}] - def self.repositories(project_root:, ruby_version_requirement: nil) - context = { project_root:, ruby_version_requirement: } - INTEGRATIONS.each_with_object({}) do |entry, repositories| - repositories[entry.symbol] = build_repository(entry, context) + class << self + # Build the integration-type -> Repository hash the Resolver consumes. + # + # @param project_root [Pathname] project root (threaded to repositories that need it) + # @param ruby_version_requirement [String, nil] for the bundler-generated Gemfile + # @return [Hash{Symbol => Repository}] + def repositories(project_root:, ruby_version_requirement: nil) + context = { project_root:, ruby_version_requirement: } + INTEGRATIONS.to_h { |entry| [entry.symbol, build_repository(entry, context)] } end - end - # Build the integration-type -> Integration hash for host installs. - # - # @param project_root [Pathname] project root (threaded to integrations that need it) - # @param cache [Cache] shared download cache (passed to every integration) - # @param taps [Array] Homebrew taps for the brew integration - # @param ruby_version_requirement [String, nil] for the bundler repository - # @param python_version [String, nil] for the pip integration's venv - # @return [Hash{Symbol => Integration}] - def self.host_integrations(project_root:, cache:, taps: [], ruby_version_requirement: nil, python_version: nil) - context = { - project_root:, - project_dir: project_root, - ruby_version_requirement:, - python_version:, - taps:, - } - INTEGRATIONS.each_with_object({}) do |entry, integrations| - next unless entry.host? + # Build the integration-type -> Integration hash for host installs. + # + # @param project_root [Pathname] project root (threaded to integrations that need it) + # @param cache [Cache] shared download cache (passed to every integration) + # @param taps [Array] Homebrew taps for the brew integration + # @param ruby_version_requirement [String, nil] for the bundler repository + # @param python_version [String, nil] for the pip integration's venv + # @return [Hash{Symbol => Integration}] + def host_integrations(project_root:, cache:, taps: [], ruby_version_requirement: nil, python_version: nil) + context = { + project_root:, + project_dir: project_root, + ruby_version_requirement:, + python_version:, + taps:, + } + INTEGRATIONS.each_with_object({}) do |entry, integrations| + next unless entry.host? - integrations[entry.symbol] = entry.integration.new( - repository: build_repository(entry, context), - cache:, - **context.slice(*entry.integration_needs), - ) + integrations[entry.symbol] = entry.integration.new( + repository: build_repository(entry, context), + cache:, + **context.slice(*entry.integration_needs), + ) + end end - end - # @param entry [Entry] - # @param context [Hash{Symbol => Object}] available constructor arguments - # @return [Repository] - def self.build_repository(entry, context) - entry.repository.new(**context.slice(*entry.repository_needs)) + # @param entry [Entry] + # @param context [Hash{Symbol => Object}] available constructor arguments + # @return [Repository] + def build_repository(entry, context) + entry.repository.new(**context.slice(*entry.repository_needs)) + end end end end diff --git a/lib/dev/deps/steam_integration.rb b/lib/dev/deps/steam_integration.rb index fe8ccd2..ded97b7 100644 --- a/lib/dev/deps/steam_integration.rb +++ b/lib/dev/deps/steam_integration.rb @@ -101,8 +101,8 @@ def verify_build_id(dep, server_dir) return if installed_build == dep.version raise BuildMismatchError, - "#{dep.name}: expected buildid #{dep.version}, installed #{installed_build.inspect} " \ - "— run dev update-deps to re-pin" + "#{dep.name}: expected buildid #{dep.version}, installed #{installed_build.inspect} " \ + "— run dev update-deps to re-pin" end end end diff --git a/lib/dev/deps/xcode_integration.rb b/lib/dev/deps/xcode_integration.rb index ed185a3..f92a413 100644 --- a/lib/dev/deps/xcode_integration.rb +++ b/lib/dev/deps/xcode_integration.rb @@ -40,23 +40,25 @@ class InstallError < StandardError; end INSTALL_ROOT = "/Applications" - # The version-named app bundle the pin installs to. xcodes' default - # naming, chosen precisely because nothing auto-updates it in place. - # - # @param version [String] pinned Xcode version - # @param root [String] install root (tests point this at a tmpdir) - # @return [String] - def self.app_path(version, root: INSTALL_ROOT) - File.join(root, "Xcode-#{version}.app") - end + class << self + # The version-named app bundle the pin installs to. xcodes' default + # naming, chosen precisely because nothing auto-updates it in place. + # + # @param version [String] pinned Xcode version + # @param root [String] install root (tests point this at a tmpdir) + # @return [String] + def app_path(version, root: INSTALL_ROOT) + File.join(root, "Xcode-#{version}.app") + end - # The DEVELOPER_DIR for a pinned version. - # - # @param version [String] pinned Xcode version - # @param root [String] install root (tests point this at a tmpdir) - # @return [String] - def self.developer_dir(version, root: INSTALL_ROOT) - File.join(app_path(version, root:), "Contents", "Developer") + # The DEVELOPER_DIR for a pinned version. + # + # @param version [String] pinned Xcode version + # @param root [String] install root (tests point this at a tmpdir) + # @return [String] + def developer_dir(version, root: INSTALL_ROOT) + File.join(app_path(version, root:), "Contents", "Developer") + end end # @param repository [Repository] @@ -109,9 +111,9 @@ def ensure_metal_toolchain(version) return if run_metal_toolchain_download(version) raise InstallError, - "xcodebuild -downloadComponent MetalToolchain failed for Xcode #{version}. " \ - "Retry manually: DEVELOPER_DIR=#{self.class.developer_dir(version, root: install_root)} " \ - "xcodebuild -downloadComponent MetalToolchain" + "xcodebuild -downloadComponent MetalToolchain failed for Xcode #{version}. " \ + "Retry manually: DEVELOPER_DIR=#{self.class.developer_dir(version, root: install_root)} " \ + "xcodebuild -downloadComponent MetalToolchain" end # @param version [String] @@ -120,8 +122,8 @@ def ensure_metal_toolchain(version) def install_via_xcodes(version) unless xcodes_available? raise XcodesMissingError, - "xcode #{version}: the xcodes CLI is not installed. " \ - "Declare it in dependencies.rb — group :build { brew \"xcodes\" } — and run dev up." + "xcode #{version}: the xcodes CLI is not installed. " \ + "Declare it in dependencies.rb — group :build { brew \"xcodes\" } — and run dev up." end puts ">>> Installing Xcode #{version} via xcodes (this downloads several GB)" diff --git a/lib/dev/plan/content.rb b/lib/dev/plan/content.rb index 506f0ad..420692c 100644 --- a/lib/dev/plan/content.rb +++ b/lib/dev/plan/content.rb @@ -18,41 +18,45 @@ class Content # @return [String] markdown body (canonical plan prose) attr_reader :body - # Parse a plan file into its layers. Canonical on-disk order is header, - # then optional frontmatter, then body. When frontmatter sits above the - # ai-flow header (Cursor's plan tool writes that layout, with a blank - # line after the closing fence), both are still recognized; {#render} - # rewrites canonical order. - # - # @param content [String] - # @return [Content] - def self.parse(content) - header, remainder = Header.split(without_leading_blank_lines(content)) - if header - frontmatter, body = Frontmatter.split(remainder) - return new(header: header, frontmatter: frontmatter, body: body) - end + class << self + # Parse a plan file into its layers. Canonical on-disk order is header, + # then optional frontmatter, then body. When frontmatter sits above the + # ai-flow header (Cursor's plan tool writes that layout, with a blank + # line after the closing fence), both are still recognized; {#render} + # rewrites canonical order. + # + # @param content [String] + # @return [Content] + def parse(content) + header, remainder = Header.split(without_leading_blank_lines(content)) + if header + frontmatter, body = Frontmatter.split(remainder) + return new(header: header, frontmatter: frontmatter, body: body) + end + + # Frontmatter may sit above a misplaced ai-flow header. + frontmatter, after_frontmatter = Frontmatter.split(content) + if frontmatter + header, body = Header.split(without_leading_blank_lines(after_frontmatter)) + # No header: keep the body byte-exact (the stripped copy was only + # for detection). + return new(header: header, frontmatter: frontmatter, body: header ? body : after_frontmatter) + end - # Frontmatter may sit above a misplaced ai-flow header. - frontmatter, after_frontmatter = Frontmatter.split(content) - if frontmatter - header, body = Header.split(without_leading_blank_lines(after_frontmatter)) - # No header: keep the body byte-exact (the stripped copy was only - # for detection). - return new(header: header, frontmatter: frontmatter, body: header ? body : after_frontmatter) + new(header: nil, frontmatter: nil, body: content) end - new(header: nil, frontmatter: nil, body: content) - end + private - # The Header pattern is anchored at the start of its input, so blank - # lines ahead of the comment (Cursor writes one after its frontmatter - # fence) are skipped before detection — and only for detection. - # - # @param content [String] - # @return [String] - def self.without_leading_blank_lines(content) - content.sub(/\A(?:[ \t]*\n)+/, "") + # The Header pattern is anchored at the start of its input, so blank + # lines ahead of the comment (Cursor writes one after its frontmatter + # fence) are skipped before detection — and only for detection. + # + # @param content [String] + # @return [String] + def without_leading_blank_lines(content) + content.sub(/\A(?:[ \t]*\n)+/, "") + end end # @param header [Dev::Plan::Header, nil] diff --git a/lib/dev/plan/frontmatter.rb b/lib/dev/plan/frontmatter.rb index 148c597..07869c0 100644 --- a/lib/dev/plan/frontmatter.rb +++ b/lib/dev/plan/frontmatter.rb @@ -10,47 +10,49 @@ module Plan class Frontmatter FENCE_LINE = /\A---\n?\z/ - # Peel a Cursor-like YAML frontmatter block from the start of +content+. - # Only a leading `---` … `---` fence whose interior is a YAML mapping is - # removed; ordinary markdown horizontal rules deeper in the body, or a - # leading `---` that is not a mapping, are left alone. - # - # @param content [String] - # @return [Array(String | nil, String)] frontmatter block (including - # fences and a trailing newline after the closing fence) or nil, and - # the remainder - def self.split(content) - lines = content.lines - return [nil, content] if lines.empty? || !fence?(lines.fetch(0)) - - close_index = (1...lines.length).find { |index| fence?(lines.fetch(index)) } - return [nil, content] unless close_index - - yaml_text = lines[1...close_index].join - return [nil, content] unless mapping?(yaml_text) - - frontmatter = lines[0..close_index].join - frontmatter = "#{frontmatter}\n" unless frontmatter.end_with?("\n") - body = lines[(close_index + 1)..].join - [frontmatter, body] + class << self + # Peel a Cursor-like YAML frontmatter block from the start of +content+. + # Only a leading `---` … `---` fence whose interior is a YAML mapping is + # removed; ordinary markdown horizontal rules deeper in the body, or a + # leading `---` that is not a mapping, are left alone. + # + # @param content [String] + # @return [Array(String | nil, String)] frontmatter block (including + # fences and a trailing newline after the closing fence) or nil, and + # the remainder + def split(content) + lines = content.lines + return [nil, content] if lines.empty? || !fence?(lines.fetch(0)) + + close_index = (1...lines.length).find { |index| fence?(lines.fetch(index)) } + return [nil, content] unless close_index + + yaml_text = lines[1...close_index].join + return [nil, content] unless mapping?(yaml_text) + + frontmatter = lines[0..close_index].join + frontmatter = "#{frontmatter}\n" unless frontmatter.end_with?("\n") + body = lines[(close_index + 1)..].join + [frontmatter, body] + end + + private + + # @param line [String] + # @return [Boolean] + def fence?(line) + line.match?(FENCE_LINE) + end + + # @param yaml_text [String] interior between fences + # @return [Boolean] true when the interior parses as a YAML mapping + def mapping?(yaml_text) + parsed = YAML.safe_load(yaml_text) + parsed.is_a?(Hash) + rescue Psych::SyntaxError, Psych::DisallowedClass + false + end end - - # @param line [String] - # @return [Boolean] - def self.fence?(line) - line.match?(FENCE_LINE) - end - private_class_method :fence? - - # @param yaml_text [String] interior between fences - # @return [Boolean] true when the interior parses as a YAML mapping - def self.mapping?(yaml_text) - parsed = YAML.safe_load(yaml_text) - parsed.is_a?(Hash) - rescue Psych::SyntaxError, Psych::DisallowedClass - false - end - private_class_method :mapping? end end end diff --git a/lib/dev/plan/header.rb b/lib/dev/plan/header.rb index ba870f2..d29d150 100644 --- a/lib/dev/plan/header.rb +++ b/lib/dev/plan/header.rb @@ -49,20 +49,22 @@ def initialize(owner_repo:, number:, synced_at:) @synced_at = synced_at end - # Split a plan file's content into its header and body. - # - # @param content [String] - # @return [Array(Header | nil, String)] header (nil when unlinked) and body - def self.split(content) - match = PATTERN.match(content) - return [nil, content] unless match + class << self + # Split a plan file's content into its header and body. + # + # @param content [String] + # @return [Array(Header | nil, String)] header (nil when unlinked) and body + def split(content) + match = PATTERN.match(content) + return [nil, content] unless match - header = new( - owner_repo: match[:owner_repo], - number: Integer(match[:number]), - synced_at: match[:synced_at], - ) - [header, match.post_match] + header = new( + owner_repo: match[:owner_repo], + number: Integer(match[:number]), + synced_at: match[:synced_at], + ) + [header, match.post_match] + end end # @return [String] "owner/repo#number" diff --git a/lib/dev/plan/settings.rb b/lib/dev/plan/settings.rb index b6c82f4..0665650 100644 --- a/lib/dev/plan/settings.rb +++ b/lib/dev/plan/settings.rb @@ -32,8 +32,8 @@ def plans_repo return value if value && !value.empty? raise MissingSettingError, - "no org plans repo configured — add `plans_repo: /` " \ - "to #{@config_path} (or set DEV_PLANS_REPO)." + "no org plans repo configured — add `plans_repo: /` " \ + "to #{@config_path} (or set DEV_PLANS_REPO)." end private diff --git a/lib/dev/plan/workspace.rb b/lib/dev/plan/workspace.rb index 9797c1c..27442e8 100644 --- a/lib/dev/plan/workspace.rb +++ b/lib/dev/plan/workspace.rb @@ -57,12 +57,14 @@ def linked_plan_files end end - # @param title [String] - # @return [String] filesystem-safe slug (bounded length) - def self.slugify(title) - slug = title.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "") - slug = slug[0, 40].to_s.sub(/-\z/, "") - slug.empty? ? "plan" : slug + class << self + # @param title [String] + # @return [String] filesystem-safe slug (bounded length) + def slugify(title) + slug = title.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "") + slug = slug[0, 40].to_s.sub(/-\z/, "") + slug.empty? ? "plan" : slug + end end private diff --git a/lib/dev/runner_setup.rb b/lib/dev/runner_setup.rb index 137d2ef..ae105b6 100644 --- a/lib/dev/runner_setup.rb +++ b/lib/dev/runner_setup.rb @@ -67,14 +67,16 @@ def initialize(config:, repo: nil, org: false, executor: Executor.new, out: $std @host_platform = host_platform end - # The actions-runner release platform slug for the current host (GitHub - # names macOS "osx"). - # - # @return [String] - def self.detect_host_platform - os = RUBY_PLATFORM.include?("darwin") ? "osx" : "linux" - arch = RUBY_PLATFORM.match?(/arm64|aarch64/) ? "arm64" : "x64" - "#{os}-#{arch}" + class << self + # The actions-runner release platform slug for the current host (GitHub + # names macOS "osx"). + # + # @return [String] + def detect_host_platform + os = RUBY_PLATFORM.include?("darwin") ? "osx" : "linux" + arch = RUBY_PLATFORM.match?(/arm64|aarch64/) ? "arm64" : "x64" + "#{os}-#{arch}" + end end # Run the full setup: preflight, download, register, install the service. diff --git a/lib/shadowenv_ruby.rb b/lib/shadowenv_ruby.rb index a25a61d..36ff227 100644 --- a/lib/shadowenv_ruby.rb +++ b/lib/shadowenv_ruby.rb @@ -285,7 +285,7 @@ def generate_ruby_lisp(ruby_root, ruby_version) LISP end -# Ensure the shadowenv activation hook is in the user's shell RC. + # Ensure the shadowenv activation hook is in the user's shell RC. # # Delegates the shell → RC-file mapping and the idempotent append to the # shared Dev::ShellRcHook installer; this module only declares its snippet diff --git a/sorbet/tapioca/config.yml b/sorbet/tapioca/config.yml index 886ae58..1aebbec 100644 --- a/sorbet/tapioca/config.yml +++ b/sorbet/tapioca/config.yml @@ -1,10 +1,16 @@ gem: # Add your `gem` command parameters here: # - # exclude: - # - gem_name # doc: true # workers: 5 + # + # RuboCop is lint-time only (require: false) and never referenced from typed + # code; generating its RBIs also crashes on Ruby 4 (yard's legacy parser + # requires the removed irb/notifier). + exclude: + - rubocop + - rubocop-ast + - rubocop-shopify dsl: # Add your `dsl` command parameters here: # diff --git a/sorbet/tapioca/require.rb b/sorbet/tapioca/require.rb index 947e82c..1779a7d 100644 --- a/sorbet/tapioca/require.rb +++ b/sorbet/tapioca/require.rb @@ -6,5 +6,5 @@ # mocha/minitest expects the old MiniTest constant (test_helper.rb aliases it, but # that doesn't run during tapioca loading). require "minitest" -MiniTest = Minitest unless defined?(MiniTest) # rubocop:disable Naming/ConstantName +MiniTest = Minitest unless defined?(MiniTest) require "mocha/minitest" diff --git a/src/dev.rb b/src/dev.rb index 33acefc..f37d91c 100644 --- a/src/dev.rb +++ b/src/dev.rb @@ -7,46 +7,48 @@ # Dev CLI: find repo with dev.yml, run declared commands (optionally in a CLI::UI Frame). # Entry point: Dev::Runner.new.run(ARGV) module Dev - extend T::Sig - DEV_YAML_FILENAME = "dev.yml" class DevYamlNotFoundError < StandardError; end - - # Pathname of dev.yml current working directory. Walks back parents until it finds a dev.yml file. Memoized on first call. - sig { returns(Pathname) } - def self.dev_yaml_file - @dev_yaml_file = T.let(@dev_yaml_file, T.nilable(Pathname)) - return @dev_yaml_file if @dev_yaml_file - - result = T.let(nil, T.nilable(Pathname)) - Pathname.new(Dir.pwd).ascend do |path| - dev_yaml_path = path / DEV_YAML_FILENAME - if dev_yaml_path.exist? - result = dev_yaml_path - break - end - end - raise DevYamlNotFoundError unless result - - @dev_yaml_file = result - end # Dev's root directory DEV_ROOT = T.let(Pathname.new(File.expand_path("..", __dir__)), Pathname) # Dev's lib directory - DEV_LIB_DIR = T.let(Dev::DEV_ROOT / "lib", Pathname) - - # Target project root (directory containing dev.yml). - # - # Resolved lazily rather than as a load-time constant: requiring "dev" from - # a directory without a dev.yml must not raise during load (it would dump a - # backtrace before the CLI can print a friendly message). Callers that need - # a project hit DevYamlNotFoundError here, where it can be handled. - sig { returns(Pathname) } - def self.target_project_root - dev_yaml_file.dirname + DEV_LIB_DIR = T.let(DEV_ROOT / "lib", Pathname) + + class << self + extend T::Sig + + # Pathname of dev.yml current working directory. Walks back parents until it finds a dev.yml file. Memoized on first call. + sig { returns(Pathname) } + def dev_yaml_file + @dev_yaml_file = T.let(@dev_yaml_file, T.nilable(Pathname)) + return @dev_yaml_file if @dev_yaml_file + + result = T.let(nil, T.nilable(Pathname)) + Pathname.new(Dir.pwd).ascend do |path| + dev_yaml_path = path / DEV_YAML_FILENAME + if dev_yaml_path.exist? + result = dev_yaml_path + break + end + end + raise DevYamlNotFoundError unless result + + @dev_yaml_file = result + end + + # Target project root (directory containing dev.yml). + # + # Resolved lazily rather than as a load-time constant: requiring "dev" from + # a directory without a dev.yml must not raise during load (it would dump a + # backtrace before the CLI can print a friendly message). Callers that need + # a project hit DevYamlNotFoundError here, where it can be handled. + sig { returns(Pathname) } + def target_project_root + dev_yaml_file.dirname + end end end @@ -57,5 +59,3 @@ def self.target_project_root require_relative "dev/cli" require_relative "dev/command_runner" require_relative "dev/runner" - - diff --git a/src/dev/command_registry.rb b/src/dev/command_registry.rb index 5771929..5ba0e7b 100644 --- a/src/dev/command_registry.rb +++ b/src/dev/command_registry.rb @@ -65,6 +65,5 @@ def lookup(name) def all @commands.dup end - end end diff --git a/src/dev/runner.rb b/src/dev/runner.rb index bf6f04d..7785578 100644 --- a/src/dev/runner.rb +++ b/src/dev/runner.rb @@ -27,7 +27,7 @@ class UnsupportedDevYamlRubyError < RuntimeError; end def initialize( dev_yaml_path: Dev.dev_yaml_file, cfg_parser: Dev::ConfigParser.new(command_parser: Dev::CommandParser.new) - ) + ) @cfg_parser = T.let(cfg_parser, Dev::ConfigParser) @config = T.let(@cfg_parser.parse(dev_yaml_path), Dev::Config) @registry = T.let(build_registry(@config), Dev::CommandRegistry) diff --git a/test/dev/command_test.rb b/test/dev/command_test.rb index f65ed97..4cac525 100644 --- a/test/dev/command_test.rb +++ b/test/dev/command_test.rb @@ -64,7 +64,7 @@ class CommandTest < Minitest::Test result == expected Where - cmd1 | cmd2 | expected + cmd1 | cmd2 | expected Dev::ShellCommand.new(run: "r1", desc: "d1", repl: false) | Dev::ShellCommand.new(run: "r1", desc: "d1", repl: false) | true Dev::ShellCommand.new(run: "r1", desc: "d1", repl: false) | Dev::ShellCommand.new(run: "r1", desc: "d1", repl: true) | false Dev::ShellCommand.new(run: "r1", desc: "d1", repl: false) | Dev::ShellCommand.new(run: "r1", desc: "d2", repl: false) | false @@ -90,7 +90,7 @@ class CommandTest < Minitest::Test cmd.eql?(other) == expected Where - other | expected + other | expected Dev::ShellCommand.new(run: "r1", desc: "d1", repl: false) | true Dev::ShellCommand.new(run: "r1", desc: "d1", repl: true) | false Dev::ShellCommand.new(run: "r1", desc: "d2", repl: false) | false @@ -107,7 +107,7 @@ class CommandTest < Minitest::Test result == expected Where - cmd1 | cmd2 | expected + cmd1 | cmd2 | expected Dev::ShellCommand.new(run: "r1", desc: "d1", repl: false) | Dev::ShellCommand.new(run: "r1", desc: "d1", repl: false) | true Dev::ShellCommand.new(run: "r1", desc: "d1", repl: false) | Dev::ShellCommand.new(run: "r1", desc: "d1", repl: true) | false Dev::ShellCommand.new(run: "r1", desc: "d1", repl: false) | Dev::ShellCommand.new(run: "r1", desc: "d2", repl: false) | false diff --git a/test/dev/credentials_test.rb b/test/dev/credentials_test.rb index 18cf2de..a3fcc92 100644 --- a/test/dev/credentials_test.rb +++ b/test/dev/credentials_test.rb @@ -169,7 +169,7 @@ class Dev::CredentialsTest < Minitest::Test stdout = "my-secret-key\n" Open3.stubs(:capture3) .with("security", "find-generic-password", - "-a", "d3mlabs/dev", "-s", "curseforge/api_key", "-w") + "-a", "d3mlabs/dev", "-s", "curseforge/api_key", "-w") .returns([stdout, "", stub(success?: true)]) When "loading from keychain" @@ -183,7 +183,7 @@ class Dev::CredentialsTest < Minitest::Test Given "no keychain entry" Open3.stubs(:capture3) .with("security", "find-generic-password", - "-a", "d3mlabs/dev", "-s", "curseforge/api_key", "-w") + "-a", "d3mlabs/dev", "-s", "curseforge/api_key", "-w") .returns(["", "security: SecKeychainSearchCopyNext", stub(success?: false)]) When "loading from keychain" diff --git a/test/dev/deps/brew_integration_test.rb b/test/dev/deps/brew_integration_test.rb index d36fcb3..fcf17d9 100644 --- a/test/dev/deps/brew_integration_test.rb +++ b/test/dev/deps/brew_integration_test.rb @@ -18,7 +18,7 @@ class Dev::Deps::BrewIntegrationTest < Minitest::Test integration = Dev::Deps::BrewIntegration.new(repository: repository, cache: cache) deps = [ Dev::Deps::Dependency.new(name: "cmake", integration: :brew, group: :build, - version: "3.31.4", hash: "SHA256=abc", metadata: {}), + version: "3.31.4", hash: "SHA256=abc", metadata: {}), ] Dev::Deps::BrewIntegration.any_instance.stubs(:brew_installed?).returns(false) @@ -41,7 +41,7 @@ class Dev::Deps::BrewIntegrationTest < Minitest::Test integration = Dev::Deps::BrewIntegration.new(repository: Dev::Deps::BrewRepository.new, cache: cache) deps = [ Dev::Deps::Dependency.new(name: "cmake", integration: :brew, group: :build, - version: "4.3.4", hash: "SHA256=abc", metadata: {}), + version: "4.3.4", hash: "SHA256=abc", metadata: {}), ] integration.stubs(:brew_installed?).returns(false) Open3.expects(:capture3).with("brew", "install", "cmake").returns(["", "", stub(success?: true)]) @@ -63,8 +63,8 @@ class Dev::Deps::BrewIntegrationTest < Minitest::Test integration = Dev::Deps::BrewIntegration.new(repository: Dev::Deps::BrewRepository.new, cache: cache) deps = [ Dev::Deps::Dependency.new(name: "llvm", integration: :brew, group: :build, - version: "18.1.8", hash: "SHA256=abc", - metadata: { "version_suffix" => "18" }), + version: "18.1.8", hash: "SHA256=abc", + metadata: { "version_suffix" => "18" }), ] integration.stubs(:brew_installed?).returns(false) Open3.expects(:capture3).with("brew", "install", "llvm@18").returns(["", "", stub(success?: true)]) @@ -87,7 +87,7 @@ class Dev::Deps::BrewIntegrationTest < Minitest::Test integration = Dev::Deps::BrewIntegration.new(repository: repository, cache: cache) deps = [ Dev::Deps::Dependency.new(name: "bad_formula", integration: :brew, group: :build, - version: "1.0.0", hash: nil, metadata: { "version_suffix" => "1" }), + version: "1.0.0", hash: nil, metadata: { "version_suffix" => "1" }), ] integration.stubs(:brew_installed?).returns(false) diff --git a/test/dev/deps/bundler_integration_test.rb b/test/dev/deps/bundler_integration_test.rb index b88cdd3..d713277 100644 --- a/test/dev/deps/bundler_integration_test.rb +++ b/test/dev/deps/bundler_integration_test.rb @@ -21,7 +21,7 @@ def build_integration(dir) def gem_dep(name) Dev::Deps::Dependency.new(name: name, integration: :bundler, group: :app, - version: "1.0.0", hash: nil, metadata: {}) + version: "1.0.0", hash: nil, metadata: {}) end test "install_all runs a frozen bundle install against the generated Gemfile" do diff --git a/test/dev/deps/cmake_integration_test.rb b/test/dev/deps/cmake_integration_test.rb index 9d855e9..8931d98 100644 --- a/test/dev/deps/cmake_integration_test.rb +++ b/test/dev/deps/cmake_integration_test.rb @@ -94,11 +94,11 @@ def prepopulate_dep(root, name) prepopulate_dep(dir, "gtest") deps = [ Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "sha1", hash: nil, - metadata: { "repo" => "https://github.com/boost/boost" }), + version: "sha1", hash: nil, + metadata: { "repo" => "https://github.com/boost/boost" }), Dev::Deps::Dependency.new(name: "gtest", integration: :cmake, group: :test, - version: "sha2", hash: nil, - metadata: { "repo" => "https://github.com/google/googletest" }), + version: "sha2", hash: nil, + metadata: { "repo" => "https://github.com/google/googletest" }), ] When "installing all" diff --git a/test/dev/deps/config_test.rb b/test/dev/deps/config_test.rb index a19a740..bd3ca1e 100644 --- a/test/dev/deps/config_test.rb +++ b/test/dev/deps/config_test.rb @@ -119,13 +119,13 @@ class Dev::Deps::ConfigTest < Minitest::Test config = Dev::Deps.define do group :app do cmake "boost", - url: "https://example.com/boost.tar.gz", - tag: "boost-1.90.0", - cmake_targets: ["stacktrace"], - cmake_namespace: "Boost::" + url: "https://example.com/boost.tar.gz", + tag: "boost-1.90.0", + cmake_targets: ["stacktrace"], + cmake_namespace: "Boost::" cmake "cereal", - repo: "https://github.com/USCiLab/cereal", - tag: "v1.3.2" + repo: "https://github.com/USCiLab/cereal", + tag: "v1.3.2" end end @@ -149,9 +149,9 @@ class Dev::Deps::ConfigTest < Minitest::Test config = Dev::Deps.define do group :test do cmake "googletest", - repo: "https://github.com/google/googletest", - tag: "v1.17.0", - cmake_targets: ["gtest", "gmock"] + repo: "https://github.com/google/googletest", + tag: "v1.17.0", + cmake_targets: ["gtest", "gmock"] end end @@ -175,8 +175,8 @@ class Dev::Deps::ConfigTest < Minitest::Test config = Dev::Deps.define do group :app do cmake "entityx", - repo: "https://github.com/alecthomas/entityx", - commit: "ee3042f8b027" + repo: "https://github.com/alecthomas/entityx", + commit: "ee3042f8b027" end end diff --git a/test/dev/deps/dependency_installer_test.rb b/test/dev/deps/dependency_installer_test.rb index 7a8e907..a525b19 100644 --- a/test/dev/deps/dependency_installer_test.rb +++ b/test/dev/deps/dependency_installer_test.rb @@ -28,7 +28,7 @@ class Dev::Deps::DependencyInstallerTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: Pathname(dir)) deps = [ Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "1.90.0", hash: "SHA256=aaa", metadata: {}), + version: "1.90.0", hash: "SHA256=aaa", metadata: {}), ] lockfile.lock(deps) cmake_integration = RecordingIntegration.new @@ -53,9 +53,9 @@ class Dev::Deps::DependencyInstallerTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: Pathname(dir)) deps = [ Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "1.90.0", hash: "SHA256=aaa", metadata: {}), + version: "1.90.0", hash: "SHA256=aaa", metadata: {}), Dev::Deps::Dependency.new(name: "ccache", integration: :brew, group: :build, - version: "4.10", hash: "SHA256=bbb", metadata: {}), + version: "4.10", hash: "SHA256=bbb", metadata: {}), ] lockfile.lock(deps) @@ -92,13 +92,13 @@ class Dev::Deps::DependencyInstallerTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: Pathname(dir)) deps = [ Dev::Deps::Dependency.new(name: "cmake", integration: :brew, group: :build, - version: "3.31", hash: "SHA256=aaa", metadata: {}), + version: "3.31", hash: "SHA256=aaa", metadata: {}), Dev::Deps::Dependency.new(name: "ruby", integration: :brew, group: :build, - version: "4.0", hash: "SHA256=bbb", - metadata: { "env" => "ci" }), + version: "4.0", hash: "SHA256=bbb", + metadata: { "env" => "ci" }), Dev::Deps::Dependency.new(name: "powershell", integration: :brew, group: :build, - version: "7.4", hash: "SHA256=ccc", - metadata: { "env" => "dev" }), + version: "7.4", hash: "SHA256=ccc", + metadata: { "env" => "dev" }), ] lockfile.lock(deps) brew_int = RecordingIntegration.new @@ -123,13 +123,13 @@ class Dev::Deps::DependencyInstallerTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: Pathname(dir)) deps = [ Dev::Deps::Dependency.new(name: "cmake", integration: :brew, group: :build, - version: "3.31", hash: "SHA256=aaa", metadata: {}), + version: "3.31", hash: "SHA256=aaa", metadata: {}), Dev::Deps::Dependency.new(name: "UnrealEngine", integration: :gh, group: :game, - version: "5.8.0-wine-7", hash: nil, - metadata: { "host" => "linux" }), + version: "5.8.0-wine-7", hash: nil, + metadata: { "host" => "linux" }), Dev::Deps::Dependency.new(name: "UnrealEngineMac", integration: :gh, group: :editor, - version: "5.8.0-mac-editor-1", hash: nil, - metadata: { "host" => "darwin" }), + version: "5.8.0-mac-editor-1", hash: nil, + metadata: { "host" => "darwin" }), ] lockfile.lock(deps) brew_int = RecordingIntegration.new @@ -155,11 +155,11 @@ class Dev::Deps::DependencyInstallerTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: Pathname(dir)) deps = [ Dev::Deps::Dependency.new(name: "ffi", integration: :bundler, group: :app, - version: "1.17.0", hash: nil, metadata: {}), + version: "1.17.0", hash: nil, metadata: {}), Dev::Deps::Dependency.new(name: "luaunit", integration: :luarocks, group: :test, - version: "3.5-1", hash: "SHA256=aaa", metadata: {}), + version: "3.5-1", hash: "SHA256=aaa", metadata: {}), Dev::Deps::Dependency.new(name: "cmake", integration: :brew, group: :build, - version: "3.31", hash: "SHA256=bbb", metadata: {}), + version: "3.31", hash: "SHA256=bbb", metadata: {}), ] lockfile.lock(deps) bundler_int = RecordingIntegration.new @@ -187,7 +187,7 @@ class Dev::Deps::DependencyInstallerTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: Pathname(dir)) deps = [ Dev::Deps::Dependency.new(name: "foo", integration: :unknown, group: :app, - version: "1.0", hash: "SHA256=aaa", metadata: {}), + version: "1.0", hash: "SHA256=aaa", metadata: {}), ] lockfile.lock(deps) installer = Dev::Deps::DependencyInstaller.new(lockfile:, integrations: {}) diff --git a/test/dev/deps/dsl_test.rb b/test/dev/deps/dsl_test.rb index f057506..5eb149a 100644 --- a/test/dev/deps/dsl_test.rb +++ b/test/dev/deps/dsl_test.rb @@ -11,8 +11,8 @@ class Dev::Deps::DSLTest < Minitest::Test config = Dev::Deps.define do group :app do cmake "boost", - url: "https://example.com/boost.tar.gz", - tag: "boost-1.90.0" + url: "https://example.com/boost.tar.gz", + tag: "boost-1.90.0" end end @@ -205,10 +205,10 @@ class Dev::Deps::DSLTest < Minitest::Test config = Dev::Deps.define do group :editor, host: :darwin do gh "UnrealEngineMac", - github: "d3mlabs/unreal-engine", - tag: "5.8.0-mac-editor-1", - assets: "UnrealEngine-Editor-Mac.tar.zst.*", - install_dir: "~/.dev/engines/ue5-mac" + github: "d3mlabs/unreal-engine", + tag: "5.8.0-mac-editor-1", + assets: "UnrealEngine-Editor-Mac.tar.zst.*", + install_dir: "~/.dev/engines/ue5-mac" xcode "26.1.1" end end @@ -224,11 +224,11 @@ class Dev::Deps::DSLTest < Minitest::Test config = Dev::Deps.define do group :game do gh "UnrealEngine", - github: "d3mlabs/unreal-engine", - tag: "5.8.0-wine-7", - assets: "UnrealEngine-Wine-Editor-Linux.tar.zst.*", - install_dir: "~/.dev/engines/ue5", - host: :linux + github: "d3mlabs/unreal-engine", + tag: "5.8.0-wine-7", + assets: "UnrealEngine-Wine-Editor-Linux.tar.zst.*", + install_dir: "~/.dev/engines/ue5", + host: :linux end end @@ -276,9 +276,9 @@ class Dev::Deps::DSLTest < Minitest::Test config = Dev::Deps.define do group :build do gh "satisfactorymodding/UnrealEngine", - tag: "5.6.1-css-83", - assets: "UnrealEngine-CSS-Editor-Linux.tar.zst.*", - install_dir: "~/.dev/engines/unreal-engine-css" + tag: "5.6.1-css-83", + assets: "UnrealEngine-CSS-Editor-Linux.tar.zst.*", + install_dir: "~/.dev/engines/unreal-engine-css" end end @@ -298,10 +298,10 @@ class Dev::Deps::DSLTest < Minitest::Test config = Dev::Deps.define do group :game do gh "UnrealEngine", - github: "EpicGames/UnrealEngine", - tag: "5.6.1-release", - build: "bin/build-ue.sh", - install_dir: "~/.dev/engines/ue5" + github: "EpicGames/UnrealEngine", + tag: "5.6.1-release", + build: "bin/build-ue.sh", + install_dir: "~/.dev/engines/ue5" end end @@ -322,7 +322,7 @@ class Dev::Deps::DSLTest < Minitest::Test config = Dev::Deps.define do group :app do gh "json", github: "nlohmann/json", tag: "v3.11.3", build: :none, - install_dir: "~/.dev/headers/json" + install_dir: "~/.dev/headers/json" end end @@ -335,7 +335,7 @@ class Dev::Deps::DSLTest < Minitest::Test Dev::Deps.define do group :game do gh "UnrealEngine", github: "EpicGames/UnrealEngine", tag: "5.6.1-release", - install_dir: "~/.dev/engines/ue5" + install_dir: "~/.dev/engines/ue5" end end @@ -348,7 +348,7 @@ class Dev::Deps::DSLTest < Minitest::Test Dev::Deps.define do group :game do gh "UnrealEngine", github: "EpicGames/UnrealEngine", tag: "5.6.1-release", - assets: "*.tar.zst.*", build: "bin/build-ue.sh", install_dir: "~/.dev/engines/ue5" + assets: "*.tar.zst.*", build: "bin/build-ue.sh", install_dir: "~/.dev/engines/ue5" end end @@ -467,7 +467,7 @@ class Dev::Deps::DSLTest < Minitest::Test config = Dev::Deps.define do group :test do cmake "googletest", github: "google/googletest", tag: "v1.17.0", - post_install: hook + post_install: hook end end diff --git a/test/dev/deps/lockfile_test.rb b/test/dev/deps/lockfile_test.rb index 6f37211..8a42b8a 100644 --- a/test/dev/deps/lockfile_test.rb +++ b/test/dev/deps/lockfile_test.rb @@ -139,11 +139,11 @@ class Dev::Deps::LockfileTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: dir) deps = [ Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "1.90.0", hash: "SHA256=aaa", metadata: {}), + version: "1.90.0", hash: "SHA256=aaa", metadata: {}), Dev::Deps::Dependency.new(name: "luaunit", integration: :luarocks, group: :test, - version: "3.5-1", hash: "SHA256=bbb", metadata: {}), + version: "3.5-1", hash: "SHA256=bbb", metadata: {}), Dev::Deps::Dependency.new(name: "cmake", integration: :brew, group: :build, - version: "3.31.4", hash: "SHA256=ccc", metadata: {}), + version: "3.31.4", hash: "SHA256=ccc", metadata: {}), ] When "locking and reading back" @@ -166,16 +166,16 @@ class Dev::Deps::LockfileTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: dir) deps = [ Dev::Deps::Dependency.new(name: "ccache", integration: :brew, group: :build, - version: "4.10.2", hash: "SHA256=aaa", metadata: {}), + version: "4.10.2", hash: "SHA256=aaa", metadata: {}), Dev::Deps::Dependency.new(name: "ruby", integration: :brew, group: :build, - version: "3.3.0", hash: "SHA256=bbb", - metadata: { "env" => "ci" }), + version: "3.3.0", hash: "SHA256=bbb", + metadata: { "env" => "ci" }), Dev::Deps::Dependency.new(name: "powershell", integration: :brew, group: :build, - version: "7.4.0", hash: "SHA256=ccc", - metadata: { "env" => "ci", "tap" => "d3mlabs/d3mlabs" }), + version: "7.4.0", hash: "SHA256=ccc", + metadata: { "env" => "ci", "tap" => "d3mlabs/d3mlabs" }), Dev::Deps::Dependency.new(name: "powershell", integration: :brew, group: :build, - version: nil, hash: nil, - metadata: { "env" => "dev", "cask" => true }), + version: nil, hash: nil, + metadata: { "env" => "dev", "cask" => true }), ] When "locking" @@ -199,12 +199,12 @@ class Dev::Deps::LockfileTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: dir) deps = [ Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "1.90.0", hash: "SHA256=aaa", metadata: {}), + version: "1.90.0", hash: "SHA256=aaa", metadata: {}), Dev::Deps::Dependency.new(name: "mock-server", integration: :custom, group: :staging, - version: "2.0.0", hash: "SHA256=bbb", metadata: {}), + version: "2.0.0", hash: "SHA256=bbb", metadata: {}), Dev::Deps::Dependency.new(name: "load-gen", integration: :custom, group: :integration, - version: "1.0.0", hash: "SHA256=ccc", - metadata: { "url" => "https://example.com/load-gen.tar.gz" }), + version: "1.0.0", hash: "SHA256=ccc", + metadata: { "url" => "https://example.com/load-gen.tar.gz" }), ] When "locking and reading back" @@ -233,10 +233,10 @@ class Dev::Deps::LockfileTest < Minitest::Test lockfile = Dev::Deps::Lockfile.new(dir: dir) deps = [ Dev::Deps::Dependency.new(name: "ccache", integration: :brew, group: :build, - version: "4.10.2", hash: "SHA256=aaa", metadata: {}), + version: "4.10.2", hash: "SHA256=aaa", metadata: {}), Dev::Deps::Dependency.new(name: "ruby", integration: :brew, group: :build, - version: "3.3.0", hash: "SHA256=bbb", - metadata: { "env" => "ci" }), + version: "3.3.0", hash: "SHA256=bbb", + metadata: { "env" => "ci" }), ] When "locking and reading back" diff --git a/test/dev/deps/luarocks_integration_test.rb b/test/dev/deps/luarocks_integration_test.rb index cf0fdf9..c3c2e17 100644 --- a/test/dev/deps/luarocks_integration_test.rb +++ b/test/dev/deps/luarocks_integration_test.rb @@ -16,10 +16,10 @@ class Dev::Deps::LuaRocksIntegrationTest < Minitest::Test cache = Dev::Deps::Cache.new(cache_dir: dir) repository = Dev::Deps::LuaRocksRepository.new integration = Dev::Deps::LuaRocksIntegration.new(repository: repository, cache: cache, - project_root: Pathname(dir)) + project_root: Pathname(dir)) deps = [ Dev::Deps::Dependency.new(name: "luaunit", integration: :luarocks, group: :test, - version: "3.5-1", hash: "SHA256=abc", metadata: {}), + version: "3.5-1", hash: "SHA256=abc", metadata: {}), ] tree = (Pathname(dir) / "lua_modules").to_s Open3.expects(:capture3) @@ -42,10 +42,10 @@ class Dev::Deps::LuaRocksIntegrationTest < Minitest::Test cache = Dev::Deps::Cache.new(cache_dir: dir) repository = Dev::Deps::LuaRocksRepository.new integration = Dev::Deps::LuaRocksIntegration.new(repository: repository, cache: cache, - project_root: Pathname(dir)) + project_root: Pathname(dir)) deps = [ Dev::Deps::Dependency.new(name: "badrock", integration: :luarocks, group: :runtime, - version: "1.0", hash: "SHA256=def", metadata: {}), + version: "1.0", hash: "SHA256=def", metadata: {}), ] failed_status = stub(success?: false) Open3.stubs(:capture3).returns(["", "not found", failed_status]) diff --git a/test/dev/deps/luarocks_registry_test.rb b/test/dev/deps/luarocks_registry_test.rb index 971c962..0d8a393 100644 --- a/test/dev/deps/luarocks_registry_test.rb +++ b/test/dev/deps/luarocks_registry_test.rb @@ -48,7 +48,7 @@ class Dev::Deps::LuaRocksRepositoryTest < Minitest::Test When "fetching the dependency" error = assert_raises(Dev::Deps::LuaRocksRepository::SearchError) do repository.fetch("name" => "missing", "integration" => "luarocks", - "group" => "runtime", "constraint" => ">=1.0") + "group" => "runtime", "constraint" => ">=1.0") end Then "the error mentions the package name" @@ -65,7 +65,7 @@ class Dev::Deps::LuaRocksRepositoryTest < Minitest::Test When "fetching the dependency" error = assert_raises(Dev::Deps::LuaRocksRepository::NoVersionError) do repository.fetch("name" => "empty", "integration" => "luarocks", - "group" => "runtime", "constraint" => ">=1.0") + "group" => "runtime", "constraint" => ">=1.0") end Then "the error mentions the package name" diff --git a/test/dev/deps/resolver_test.rb b/test/dev/deps/resolver_test.rb index 24b65a7..5a2eb73 100644 --- a/test/dev/deps/resolver_test.rb +++ b/test/dev/deps/resolver_test.rb @@ -48,9 +48,9 @@ class Dev::Deps::ResolverTest < Minitest::Test test "calls prepare once per integration with that integration's declarations before fetching" do Given "a preparing repo with two declarations of its type" foo = Dev::Deps::Dependency.new(name: "foo", integration: :bundler, group: :app, - version: "1.0", hash: nil, metadata: {}) + version: "1.0", hash: nil, metadata: {}) bar = Dev::Deps::Dependency.new(name: "bar", integration: :bundler, group: :test, - version: "2.0", hash: nil, metadata: {}) + version: "2.0", hash: nil, metadata: {}) repo = PreparingRepository.new(deps_by_name: { "foo" => foo, "bar" => bar }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "foo", integration: :bundler, group: :app), @@ -69,9 +69,9 @@ class Dev::Deps::ResolverTest < Minitest::Test test "attaches host and env from the declaration onto resolved metadata" do Given "declarations carrying the install-scoping axes" engine = Dev::Deps::Dependency.new(name: "UnrealEngineMac", integration: :gh, group: :editor, - version: "5.8.0-mac-editor-1", hash: nil, metadata: { "repo" => "d3mlabs/unreal-engine" }) + version: "5.8.0-mac-editor-1", hash: nil, metadata: { "repo" => "d3mlabs/unreal-engine" }) ruby_dep = Dev::Deps::Dependency.new(name: "ruby", integration: :brew, group: :build, - version: "4.0", hash: nil, metadata: {}) + version: "4.0", hash: nil, metadata: {}) repo = StubRepository.new(deps_by_name: { "UnrealEngineMac" => engine, "ruby" => ruby_dep }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "UnrealEngineMac", integration: :gh, group: :editor, host: :darwin), @@ -94,14 +94,14 @@ class Dev::Deps::ResolverTest < Minitest::Test test "transitive dependencies inherit the declaring dep's host and env" do Given "a host/env-scoped dep with a transitive dependency" parent = Dev::Deps::Dependency.new(name: "parent", integration: :brew, group: :build, - version: "1.0", hash: nil, metadata: {}, - dependencies: [{ name: "child", constraint: ">= 1.0" }]) + version: "1.0", hash: nil, metadata: {}, + dependencies: [{ name: "child", constraint: ">= 1.0" }]) child = Dev::Deps::Dependency.new(name: "child", integration: :brew, group: :build, - version: "2.0", hash: nil, metadata: {}) + version: "2.0", hash: nil, metadata: {}) repo = StubRepository.new(deps_by_name: { "parent" => parent, "child" => child }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "parent", integration: :brew, group: :build, - host: :darwin, env: "ci"), + host: :darwin, env: "ci"), ] resolver = Dev::Deps::Resolver.new(repositories: { brew: repo }) @@ -117,9 +117,9 @@ class Dev::Deps::ResolverTest < Minitest::Test test "resolves a flat list of declarations with no transitive dependencies" do Given "two independent declarations" boost = Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "sha1", hash: nil, metadata: {}) + version: "sha1", hash: nil, metadata: {}) gtest = Dev::Deps::Dependency.new(name: "gtest", integration: :cmake, group: :test, - version: "sha2", hash: nil, metadata: {}) + version: "sha2", hash: nil, metadata: {}) repo = StubRepository.new(deps_by_name: { "boost" => boost, "gtest" => gtest }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "boost", integration: :cmake, group: :app), @@ -138,10 +138,10 @@ class Dev::Deps::ResolverTest < Minitest::Test test "resolves transitive dependencies via Dependency#dependencies" do Given "a parent with a transitive child" child = Dev::Deps::Dependency.new(name: "child", integration: :luarocks, group: :test, - version: "2.0", hash: "SHA256=bbb", metadata: {}) + version: "2.0", hash: "SHA256=bbb", metadata: {}) parent = Dev::Deps::Dependency.new(name: "parent", integration: :luarocks, group: :test, - version: "1.0", hash: "SHA256=aaa", metadata: {}, - dependencies: [{ name: "child", constraint: ">= 1.0" }]) + version: "1.0", hash: "SHA256=aaa", metadata: {}, + dependencies: [{ name: "child", constraint: ">= 1.0" }]) repo = StubRepository.new(deps_by_name: { "parent" => parent, "child" => child }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "parent", integration: :luarocks, group: :test), @@ -173,10 +173,10 @@ class Dev::Deps::ResolverTest < Minitest::Test test "does not duplicate already-resolved transitive deps" do Given "overlapping direct and transitive deps" a = Dev::Deps::Dependency.new(name: "a", integration: :cmake, group: :app, - version: "1.0", hash: nil, metadata: {}, - dependencies: [{ name: "b", constraint: ">= 1.0" }]) + version: "1.0", hash: nil, metadata: {}, + dependencies: [{ name: "b", constraint: ">= 1.0" }]) b = Dev::Deps::Dependency.new(name: "b", integration: :cmake, group: :app, - version: "1.0", hash: nil, metadata: {}) + version: "1.0", hash: nil, metadata: {}) repo = StubRepository.new(deps_by_name: { "a" => a, "b" => b }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "a", integration: :cmake, group: :app), @@ -195,7 +195,7 @@ class Dev::Deps::ResolverTest < Minitest::Test test "handles declarations with empty transitive dependencies" do Given "a declaration with no transitive deps" solo = Dev::Deps::Dependency.new(name: "solo", integration: :cmake, group: :app, - version: "1.0", hash: nil, metadata: {}) + version: "1.0", hash: nil, metadata: {}) repo = StubRepository.new(deps_by_name: { "solo" => solo }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "solo", integration: :cmake, group: :app), @@ -213,13 +213,13 @@ class Dev::Deps::ResolverTest < Minitest::Test test "resolves deep transitive chains beyond depth 1" do Given "A depends on B, B depends on C" c = Dev::Deps::Dependency.new(name: "c", integration: :luarocks, group: :app, - version: "3.0", hash: "SHA256=ccc", metadata: {}) + version: "3.0", hash: "SHA256=ccc", metadata: {}) b = Dev::Deps::Dependency.new(name: "b", integration: :luarocks, group: :app, - version: "2.0", hash: "SHA256=bbb", metadata: {}, - dependencies: [{ name: "c", constraint: ">= 3.0" }]) + version: "2.0", hash: "SHA256=bbb", metadata: {}, + dependencies: [{ name: "c", constraint: ">= 3.0" }]) a = Dev::Deps::Dependency.new(name: "a", integration: :luarocks, group: :app, - version: "1.0", hash: "SHA256=aaa", metadata: {}, - dependencies: [{ name: "b", constraint: ">= 2.0" }]) + version: "1.0", hash: "SHA256=aaa", metadata: {}, + dependencies: [{ name: "b", constraint: ">= 2.0" }]) repo = StubRepository.new(deps_by_name: { "a" => a, "b" => b, "c" => c }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "a", integration: :luarocks, group: :app), @@ -237,19 +237,19 @@ class Dev::Deps::ResolverTest < Minitest::Test test "resolves diamond dependencies without duplication" do Given "A depends on B and C, both depend on D" d = Dev::Deps::Dependency.new(name: "d", integration: :luarocks, group: :app, - version: "1.0", hash: "SHA256=ddd", metadata: {}) + version: "1.0", hash: "SHA256=ddd", metadata: {}) b = Dev::Deps::Dependency.new(name: "b", integration: :luarocks, group: :app, - version: "1.0", hash: "SHA256=bbb", metadata: {}, - dependencies: [{ name: "d", constraint: ">= 1.0" }]) + version: "1.0", hash: "SHA256=bbb", metadata: {}, + dependencies: [{ name: "d", constraint: ">= 1.0" }]) c = Dev::Deps::Dependency.new(name: "c", integration: :luarocks, group: :app, - version: "1.0", hash: "SHA256=ccc", metadata: {}, - dependencies: [{ name: "d", constraint: ">= 1.0" }]) + version: "1.0", hash: "SHA256=ccc", metadata: {}, + dependencies: [{ name: "d", constraint: ">= 1.0" }]) a = Dev::Deps::Dependency.new(name: "a", integration: :luarocks, group: :app, - version: "1.0", hash: "SHA256=aaa", metadata: {}, - dependencies: [ - { name: "b", constraint: ">= 1.0" }, - { name: "c", constraint: ">= 1.0" }, - ]) + version: "1.0", hash: "SHA256=aaa", metadata: {}, + dependencies: [ + { name: "b", constraint: ">= 1.0" }, + { name: "c", constraint: ">= 1.0" }, + ]) repo = StubRepository.new(deps_by_name: { "a" => a, "b" => b, "c" => c, "d" => d }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "a", integration: :luarocks, group: :app), @@ -267,11 +267,11 @@ class Dev::Deps::ResolverTest < Minitest::Test test "terminates on cyclic transitive dependencies" do Given "A depends on B, B depends on A" a = Dev::Deps::Dependency.new(name: "a", integration: :cmake, group: :app, - version: "1.0", hash: nil, metadata: {}, - dependencies: [{ name: "b", constraint: {} }]) + version: "1.0", hash: nil, metadata: {}, + dependencies: [{ name: "b", constraint: {} }]) b = Dev::Deps::Dependency.new(name: "b", integration: :cmake, group: :app, - version: "1.0", hash: nil, metadata: {}, - dependencies: [{ name: "a", constraint: {} }]) + version: "1.0", hash: nil, metadata: {}, + dependencies: [{ name: "a", constraint: {} }]) repo = StubRepository.new(deps_by_name: { "a" => a, "b" => b }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "a", integration: :cmake, group: :app), @@ -289,10 +289,10 @@ class Dev::Deps::ResolverTest < Minitest::Test test "transitive dependencies inherit parent's group" do Given "a :test parent with a transitive child" child = Dev::Deps::Dependency.new(name: "child", integration: :luarocks, group: :test, - version: "2.0", hash: "SHA256=bbb", metadata: {}) + version: "2.0", hash: "SHA256=bbb", metadata: {}) parent = Dev::Deps::Dependency.new(name: "parent", integration: :luarocks, group: :test, - version: "1.0", hash: "SHA256=aaa", metadata: {}, - dependencies: [{ name: "child", constraint: ">= 1.0" }]) + version: "1.0", hash: "SHA256=aaa", metadata: {}, + dependencies: [{ name: "child", constraint: ">= 1.0" }]) repo = StubRepository.new(deps_by_name: { "parent" => parent, "child" => child }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "parent", integration: :luarocks, group: :test), @@ -310,10 +310,10 @@ class Dev::Deps::ResolverTest < Minitest::Test test "normalizes string constraints on transitive deps to Hash" do Given "a parent whose transitive dep has a string constraint" child = Dev::Deps::Dependency.new(name: "child", integration: :luarocks, group: :app, - version: "2.0", hash: "SHA256=bbb", metadata: {}) + version: "2.0", hash: "SHA256=bbb", metadata: {}) parent = Dev::Deps::Dependency.new(name: "parent", integration: :luarocks, group: :app, - version: "1.0", hash: "SHA256=aaa", metadata: {}, - dependencies: [{ name: "child", constraint: ">= 2.0" }]) + version: "1.0", hash: "SHA256=aaa", metadata: {}, + dependencies: [{ name: "child", constraint: ">= 2.0" }]) repo = StubRepository.new(deps_by_name: { "parent" => parent, "child" => child }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "parent", integration: :luarocks, group: :app), @@ -331,12 +331,12 @@ class Dev::Deps::ResolverTest < Minitest::Test test "unions platforms across groups and resolves a duplicated dep once" do Given "SML declared in :app (no platform) and :integration (LinuxServer)" sml = Dev::Deps::Dependency.new(name: "SML", integration: :ficsit, group: :app, - version: "3.12.0", hash: nil, metadata: {}) + version: "3.12.0", hash: nil, metadata: {}) repo = StubRepository.new(deps_by_name: { "SML" => sml }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "SML", integration: :ficsit, group: :app), Dev::Deps::DependencyDeclaration.new(name: "SML", integration: :ficsit, group: :integration, - platform: "LinuxServer"), + platform: "LinuxServer"), ] resolver = Dev::Deps::Resolver.new(repositories: { ficsit: repo }) @@ -352,7 +352,7 @@ class Dev::Deps::ResolverTest < Minitest::Test test "omits platforms from the fetch id when no group pins a platform" do Given "a dep declared only in groups without a platform" boost = Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "1.0", hash: nil, metadata: {}) + version: "1.0", hash: nil, metadata: {}) repo = StubRepository.new(deps_by_name: { "boost" => boost }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "boost", integration: :cmake, group: :app), @@ -370,11 +370,11 @@ class Dev::Deps::ResolverTest < Minitest::Test Given "a declaration with a post_install hook" hook = ->(dep, root) {} dep = Dev::Deps::Dependency.new(name: "gtest", integration: :cmake, group: :test, - version: "sha1", hash: nil, metadata: {}) + version: "sha1", hash: nil, metadata: {}) repo = StubRepository.new(deps_by_name: { "gtest" => dep }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "gtest", integration: :cmake, group: :test, - post_install: hook), + post_install: hook), ] resolver = Dev::Deps::Resolver.new(repositories: { cmake: repo }) @@ -388,7 +388,7 @@ class Dev::Deps::ResolverTest < Minitest::Test test "post_install is nil when declaration has none" do Given "a declaration without post_install" dep = Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "sha1", hash: nil, metadata: {}) + version: "sha1", hash: nil, metadata: {}) repo = StubRepository.new(deps_by_name: { "boost" => dep }) declarations = [ Dev::Deps::DependencyDeclaration.new(name: "boost", integration: :cmake, group: :app), diff --git a/test/dev/deps/staleness_test.rb b/test/dev/deps/staleness_test.rb index 7d98a5f..a4882bb 100644 --- a/test/dev/deps/staleness_test.rb +++ b/test/dev/deps/staleness_test.rb @@ -21,7 +21,7 @@ def build_synced_project(dir) lockfile = Dev::Deps::Lockfile.new(dir: project) lockfile.lock( [Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "1.90.0", hash: nil, metadata: {})], + version: "1.90.0", hash: nil, metadata: {})], manifest_digest: Digest::SHA256.file(manifest).hexdigest, ) project @@ -69,7 +69,7 @@ def build_staleness(dir, project) manifest_digest = Digest::SHA256.file(File.join(project, "dependencies.rb")).hexdigest Dev::Deps::Lockfile.new(dir: project).lock( [Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "1.91.0", hash: nil, metadata: {})], + version: "1.91.0", hash: nil, metadata: {})], manifest_digest: manifest_digest, ) @@ -101,7 +101,7 @@ def build_staleness(dir, project) File.write(File.join(project, "dependencies.rb"), "group :app do\nend\n") Dev::Deps::Lockfile.new(dir: project).lock( [Dev::Deps::Dependency.new(name: "boost", integration: :cmake, group: :app, - version: "1.90.0", hash: nil, metadata: {})], + version: "1.90.0", hash: nil, metadata: {})], ) staleness = build_staleness(dir, project) staleness.stamp_installed! diff --git a/test/dev/deps/xcode_integration_test.rb b/test/dev/deps/xcode_integration_test.rb index 07b9772..ff24638 100644 --- a/test/dev/deps/xcode_integration_test.rb +++ b/test/dev/deps/xcode_integration_test.rb @@ -195,7 +195,7 @@ def build_integration(dir, **overrides) Given "xcodes fails without a TTY (it hit a prompt and read EOF)" dir = Dir.mktmpdir("dev-xcode-int-test-") integration = build_integration(dir, interactive: false, install_succeeds: false, - creates_app_on_install: false) + creates_app_on_install: false) When "installing" error = assert_raises(Dev::Deps::XcodeIntegration::InstallError) do diff --git a/test/dev/plan/accessor_test.rb b/test/dev/plan/accessor_test.rb index 89c3bdc..cd87324 100644 --- a/test/dev/plan/accessor_test.rb +++ b/test/dev/plan/accessor_test.rb @@ -633,4 +633,3 @@ def read_plan(root, name) FileUtils.rm_rf(dir) end end - diff --git a/test/dev/runner_setup_test.rb b/test/dev/runner_setup_test.rb index 06ca275..6cf2f6d 100644 --- a/test/dev/runner_setup_test.rb +++ b/test/dev/runner_setup_test.rb @@ -145,7 +145,7 @@ def capturing_executor(captured) When "running setup with a repo override" setup = Dev::RunnerSetup.new(config: config, repo: "owner/repo", executor: exec, out: silent, - host_platform: "linux-x64") + host_platform: "linux-x64") setup.run Then "config.sh registers with the minted token, the service starts, no download happens" @@ -182,7 +182,7 @@ def capturing_executor(captured) When "running setup org-wide" setup = Dev::RunnerSetup.new(config: config, repo: "owner/repo", org: true, executor: exec, - out: silent, host_platform: "osx-arm64") + out: silent, host_platform: "osx-arm64") setup.run Then "the registration token is minted at the org endpoint and config.sh targets the org URL" @@ -211,7 +211,7 @@ def capturing_executor(captured) When "re-running setup org-wide" setup = Dev::RunnerSetup.new(config: config, repo: "owner/repo", org: true, executor: exec, - out: silent, host_platform: "osx-arm64") + out: silent, host_platform: "osx-arm64") setup.run Then "the installed service is uninstalled, the remove token comes from the old repo scope, " \ @@ -260,7 +260,7 @@ def capturing_executor(captured) config = Dev::RunnerSetupConfig.new(labels: "ue-engine", dir: dir, version: "9.9.9") exec = RecordingExecutor.new(&authed_responder) setup = Dev::RunnerSetup.new(config: config, repo: "owner/repo", executor: exec, out: silent, - host_platform: "linux-x64") + host_platform: "linux-x64") When "running setup" setup.run @@ -281,7 +281,7 @@ def capturing_executor(captured) config = Dev::RunnerSetupConfig.new(labels: "macos,ue-editor", dir: dir, version: "9.9.9") exec = RecordingExecutor.new(&authed_responder) setup = Dev::RunnerSetup.new(config: config, repo: "owner/repo", executor: exec, out: silent, - host_platform: "osx-arm64") + host_platform: "osx-arm64") When "running setup" setup.run diff --git a/test/dev/runner_test.rb b/test/dev/runner_test.rb index 506e8c7..95b0d2b 100644 --- a/test/dev/runner_test.rb +++ b/test/dev/runner_test.rb @@ -250,8 +250,12 @@ class RunnerTest < Minitest::Test runner = build_runner(commands: { "up" => { "run" => "./bin/up.rb", "desc" => "Setup", "container" => false } }) Dev::Cd::HookInstaller.any_instance.stubs(:ensure_installed).returns(:already_present) execution_order = [] - runner.stubs(:install_locked_deps).with { execution_order << :builtin_install; true } - Dev::ShellCommand.any_instance.stubs(:execute).with { execution_order << :project_script; true } + runner.stubs(:install_locked_deps).with { + execution_order << :builtin_install + true } + Dev::ShellCommand.any_instance.stubs(:execute).with { + execution_order << :project_script + true } When "we run up" runner.run(["up"], ui: fake_ui) diff --git a/test/lib/build_container_test.rb b/test/lib/build_container_test.rb index f9d8804..de2afe3 100644 --- a/test/lib/build_container_test.rb +++ b/test/lib/build_container_test.rb @@ -181,12 +181,16 @@ class BuildContainerTest < Minitest::Test When "ensuring the image with a build args provider" BuildContainer.stubs(:local_image?).returns(false) BuildContainer.stubs(:pull).returns(false) - BuildContainer.stubs(:build!).with { |_tag, build_args:, **_| received_args = build_args; true } + BuildContainer.stubs(:build!).with { |_tag, build_args:, **_| + received_args = build_args + true } BuildContainer.stubs(:push!).returns(true) BuildContainer.ensure_image!( config, project_root: Pathname(dir), - build_args_provider: -> { provider_calls += 1; { "WWISE_EMAIL" => "me@example.com" } }, + build_args_provider: -> { + provider_calls += 1 + { "WWISE_EMAIL" => "me@example.com" } }, ) Then @@ -210,7 +214,9 @@ class BuildContainerTest < Minitest::Test BuildContainer.ensure_image!( config, project_root: Pathname(dir), - build_args_provider: -> { provider_calls += 1; {} }, + build_args_provider: -> { + provider_calls += 1 + {} }, ) Then @@ -226,7 +232,9 @@ class BuildContainerTest < Minitest::Test captured = nil When "building with build args" - BuildContainer.stubs(:system).with { |*argv| captured = argv; true }.returns(true) + BuildContainer.stubs(:system).with { |*argv| + captured = argv + true }.returns(true) BuildContainer.send( :build!, "img:tag", @@ -250,7 +258,9 @@ class BuildContainerTest < Minitest::Test captured = nil When "building" - BuildContainer.stubs(:system).with { |*argv| captured = argv; true }.returns(true) + BuildContainer.stubs(:system).with { |*argv| + captured = argv + true }.returns(true) BuildContainer.send(:build!, "img:tag", project_root: Pathname(dir)) Then "the non-TTY-silent auto renderer is overridden" @@ -269,7 +279,9 @@ class BuildContainerTest < Minitest::Test When "ensuring the image" BuildContainer.stubs(:local_image?).returns(true) - BuildContainer.stubs(:pull).with { |tag| pulled << tag; true } + BuildContainer.stubs(:pull).with { |tag| + pulled << tag + true } result = BuildContainer.ensure_image!(config, project_root: Pathname(dir)) Then @@ -291,8 +303,12 @@ class BuildContainerTest < Minitest::Test When "ensuring the image" BuildContainer.stubs(:local_image?).returns(false) BuildContainer.stubs(:pull).returns(false) - BuildContainer.stubs(:build!).with { |tag, **_| built << tag; true } - BuildContainer.stubs(:push!).with { |tag| pushed << tag; true } + BuildContainer.stubs(:build!).with { |tag, **_| + built << tag + true } + BuildContainer.stubs(:push!).with { |tag| + pushed << tag + true } result = BuildContainer.ensure_image!(config, project_root: Pathname(dir)) Then @@ -316,7 +332,9 @@ class BuildContainerTest < Minitest::Test BuildContainer.stubs(:local_image?).returns(false) BuildContainer.stubs(:pull).returns(false) BuildContainer.stubs(:build!).returns(true) - BuildContainer.stubs(:push!).with { |tag| pushed << tag; true } + BuildContainer.stubs(:push!).with { |tag| + pushed << tag + true } BuildContainer.ensure_image!(config, project_root: Pathname(dir), push: false) Then @@ -778,7 +796,9 @@ class BuildContainerTest < Minitest::Test captured = nil When "building with contexts and secrets" - BuildContainer.stubs(:system).with { |*argv| captured = argv; true }.returns(true) + BuildContainer.stubs(:system).with { |*argv| + captured = argv + true }.returns(true) BuildContainer.send( :build!, "img:tag", @@ -804,7 +824,9 @@ class BuildContainerTest < Minitest::Test captured = nil When "building with a secret" - BuildContainer.stubs(:system).with { |*argv| captured = argv; true }.returns(true) + BuildContainer.stubs(:system).with { |*argv| + captured = argv + true }.returns(true) BuildContainer.send( :build!, "img:tag", @@ -847,7 +869,9 @@ class BuildContainerTest < Minitest::Test config, project_root: Pathname(dir), build_args_provider: -> { {} }, - secrets_provider: -> { secret_calls += 1; { "WWISE_TOKEN" => "tok" } }, + secrets_provider: -> { + secret_calls += 1 + { "WWISE_TOKEN" => "tok" } }, ) Then @@ -872,7 +896,9 @@ class BuildContainerTest < Minitest::Test BuildContainer.ensure_image!( config, project_root: Pathname(dir), - secrets_provider: -> { secret_calls += 1; {} }, + secrets_provider: -> { + secret_calls += 1 + {} }, ) Then @@ -1118,7 +1144,9 @@ class BuildContainerTest < Minitest::Test captured = nil When "creating the service container" - BuildContainer.stubs(:system).with { |*argv, **_kw| captured = argv; true }.returns(true) + BuildContainer.stubs(:system).with { |*argv, **_kw| + captured = argv + true }.returns(true) BuildContainer.send( :create_service_container, "dev-x", "img:tag", project_root: Pathname("/project"), volumes: ["/engines/ue:/ue"],