From 06089ec03c0229cd93ed164f791d2f0e480bd421 Mon Sep 17 00:00:00 2001 From: Trey Hyde Date: Wed, 29 Jul 2026 11:31:21 -0700 Subject: [PATCH 1/5] feat: modernize packaging, multi-arch CI, and Fulcrum coverage Align the gem for GitHub Packages release-please publishing, replace Travis with amd64/arm64 CI, fix Ruby 3 SWIG allocator warnings and GDAL 3.13 compile breaks, and add fixture specs that mirror Fulcrum shapefile import usage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 54 ++++++ .github/workflows/release.yml | 68 +++++++ .gitignore | 2 + .release-please-manifest.json | 3 + .travis.yml | 10 - CHANGELOG.md | 38 +++- README.md | 123 ++++++++----- Rakefile | 7 +- ext/gdal-ruby/extconf_helper.rb | 51 ++++++ ext/gdal-ruby/gdal/extconf.rb | 31 +--- ext/gdal-ruby/gdal/gdal.cpp | 13 +- ext/gdal-ruby/gdalconst/extconf.rb | 25 +-- ext/gdal-ruby/gdalconst/gdalconst.c | 9 +- ext/gdal-ruby/ogr/extconf.rb | 30 +-- ext/gdal-ruby/ogr/ogr.cpp | 9 +- ext/gdal-ruby/osr/extconf.rb | 30 +-- ext/gdal-ruby/osr/osr.cpp | 15 +- gdal.gemspec | 55 ++++-- lib/gdal-ruby/version.rb | 6 +- release-please-config.json | 12 ++ spec/fixtures/shapefiles/flat/points.dbf | Bin 0 -> 550 bytes spec/fixtures/shapefiles/flat/points.prj | 1 + spec/fixtures/shapefiles/flat/points.shp | Bin 0 -> 172 bytes spec/fixtures/shapefiles/flat/points.shx | Bin 0 -> 116 bytes spec/fixtures/shapefiles/lines/lines.dbf | Bin 0 -> 356 bytes spec/fixtures/shapefiles/lines/lines.prj | 1 + spec/fixtures/shapefiles/lines/lines.shp | Bin 0 -> 204 bytes spec/fixtures/shapefiles/lines/lines.shx | Bin 0 -> 108 bytes .../shapefiles/multipoint/multipoint.dbf | Bin 0 -> 356 bytes .../shapefiles/multipoint/multipoint.prj | 1 + .../shapefiles/multipoint/multipoint.shp | Bin 0 -> 180 bytes .../shapefiles/multipoint/multipoint.shx | Bin 0 -> 108 bytes spec/fixtures/shapefiles/points/points.dbf | Bin 0 -> 550 bytes spec/fixtures/shapefiles/points/points.prj | 1 + spec/fixtures/shapefiles/points/points.shp | Bin 0 -> 172 bytes spec/fixtures/shapefiles/points/points.shx | Bin 0 -> 116 bytes .../fixtures/shapefiles/polygons/polygons.dbf | Bin 0 -> 356 bytes .../fixtures/shapefiles/polygons/polygons.prj | 1 + .../fixtures/shapefiles/polygons/polygons.shp | Bin 0 -> 236 bytes .../fixtures/shapefiles/polygons/polygons.shx | Bin 0 -> 108 bytes spec/fulcrum_shapefile_import_spec.rb | 172 ++++++++++++++++++ spec/gdal_spec.rb | 22 ++- spec/ruby3_warnings_spec.rb | 31 ++++ spec/spec_helper.rb | 24 +++ spec/support/fulcrum_shapefile_importer.rb | 111 +++++++++++ 45 files changed, 762 insertions(+), 194 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .release-please-manifest.json delete mode 100644 .travis.yml create mode 100644 ext/gdal-ruby/extconf_helper.rb create mode 100644 release-please-config.json create mode 100644 spec/fixtures/shapefiles/flat/points.dbf create mode 100644 spec/fixtures/shapefiles/flat/points.prj create mode 100644 spec/fixtures/shapefiles/flat/points.shp create mode 100644 spec/fixtures/shapefiles/flat/points.shx create mode 100644 spec/fixtures/shapefiles/lines/lines.dbf create mode 100644 spec/fixtures/shapefiles/lines/lines.prj create mode 100644 spec/fixtures/shapefiles/lines/lines.shp create mode 100644 spec/fixtures/shapefiles/lines/lines.shx create mode 100644 spec/fixtures/shapefiles/multipoint/multipoint.dbf create mode 100644 spec/fixtures/shapefiles/multipoint/multipoint.prj create mode 100644 spec/fixtures/shapefiles/multipoint/multipoint.shp create mode 100644 spec/fixtures/shapefiles/multipoint/multipoint.shx create mode 100644 spec/fixtures/shapefiles/points/points.dbf create mode 100644 spec/fixtures/shapefiles/points/points.prj create mode 100644 spec/fixtures/shapefiles/points/points.shp create mode 100644 spec/fixtures/shapefiles/points/points.shx create mode 100644 spec/fixtures/shapefiles/polygons/polygons.dbf create mode 100644 spec/fixtures/shapefiles/polygons/polygons.prj create mode 100644 spec/fixtures/shapefiles/polygons/polygons.shp create mode 100644 spec/fixtures/shapefiles/polygons/polygons.shx create mode 100644 spec/fulcrum_shapefile_import_spec.rb create mode 100644 spec/ruby3_warnings_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 spec/support/fulcrum_shapefile_importer.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c29f269 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Ruby ${{ matrix.ruby }} / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + ruby: ['3.2', '3.3'] + os: [ubuntu-24.04, ubuntu-24.04-arm] + include: + - ruby: '3.3' + os: macos-14 + + steps: + - uses: actions/checkout@v4 + + - name: Install GDAL (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends libgdal-dev gdal-bin gdal-data pkg-config + + - name: Install GDAL (macOS) + if: runner.os == 'macOS' + run: brew install gdal pkg-config + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + + - name: Compile and test + run: bundle exec rake spec + + - name: Show linked GDAL + run: | + gdal-config --version + gdal-config --libs + ruby -e "require 'gdal'; puts Gdal::Ruby::VERSION" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d3df602 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: Release + +on: + push: + branches: [main] + +permissions: + contents: write + packages: write + pull-requests: write + +concurrency: + group: release-main + cancel-in-progress: false + +jobs: + release-please: + name: Release please + runs-on: ubuntu-24.04 + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + publish: + name: Publish gem to GitHub Packages + needs: release-please + if: needs.release-please.outputs.release_created == 'true' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Install GDAL build deps + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends libgdal-dev gdal-bin gdal-data pkg-config + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true + + - name: Build gem + run: | + gem build gdal.gemspec + ls -la gdal-*.gem + + - name: Publish to GitHub Packages + env: + GEM_HOST_API_KEY: Bearer ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir -p ~/.gem + printf '%s\n' '---' ":github: ${GEM_HOST_API_KEY}" > ~/.gem/credentials + chmod 0600 ~/.gem/credentials + gem push --key github --host https://rubygems.pkg.github.com/fulcrumapp gdal-*.gem + + - name: Attach gem to GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload "${{ needs.release-please.outputs.tag_name }}" gdal-*.gem --clobber diff --git a/.gitignore b/.gitignore index ccb6fa9..463881b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ lib/bundler/man pkg rdoc spec/reports +spec/examples.txt test/tmp test/version_tmp tmp +.tool-versions diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..ada7355 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "3.1.0" +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3b2f14a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: ruby -rvm: - - 1.9.2 - - 1.9.3 - - 2.1.5 - - 2.2.1 -before_install: - - sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable -y - - sudo apt-get update - - sudo apt-get install libgdal-dev diff --git a/CHANGELOG.md b/CHANGELOG.md index f6da7f3..fff0586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,40 @@ +# Changelog + +## [3.1.0](https://github.com/fulcrumapp/gdal-ruby/compare/v3.0.0...v3.1.0) (2026-07-29) + +### Features + +* publish to GitHub Packages with release-please on main +* multi-arch GitHub Actions CI (ubuntu amd64/arm64, macOS arm64) +* modernize gemspec metadata and require Ruby >= 3.1 +* add fixture-based specs covering Fulcrum shapefile import call surface + +### Bug Fixes + +* align in-repo version with the 3.x line Fulcrum already locks +* silence Ruby 3.2+ SWIG `T_DATA` allocator warnings on require +* compile against GDAL 3.13 (`CSLConstList` const-correctness, `ABS` shim) +* stop passing `-std=c++17` into the pure-C `gdalconst` extension + +### Documentation + +* document multi-arch limits, Packages install, and rgeo-shapefile migration path +* remove obsolete Travis CI configuration + +## [3.0.0](https://github.com/fulcrumapp/gdal-ruby/compare/v2.0.0...v3.0.0) (2020-04-16) + +* RubyGems release used by Fulcrum (`~> 3.0.0`); version file in git had drifted to 2.0.0 + +## [2.0.0](https://github.com/fulcrumapp/gdal-ruby/compare/v1.0.0...v2.0.0) (2019-06-18) + +* Fix GDAL 2.x compatibility + ## v1.0.0 + * Regenerated bindings using GDAL 1.10.1 sources and SWIG 3.0.5 -* Patch for ruby 2.2.1 (Thanks @aleksejleonov and @johnjohndoe) (Issue #5) -* Fix symbol conflicts when requiring both `gdal-ruby/gdal` and `gdal-ruby/ogr` with `get_driver_by_name` and other functions (Issue #2) -* Since the symbol conflicts are now fixed, `gdal`, `ogr`, `osr` and `gdalconst` are now `require`'d be default (Issue #2) +* Patch for ruby 2.2.1 +* Fix symbol conflicts between gdal and ogr modules ## v0.0.7 + * Fix for building on ruby versions where `$CXXFLAGS` isn't defined diff --git a/README.md b/README.md index b824051..8273864 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,107 @@ -# gdal-ruby [![Build Status](https://secure.travis-ci.org/zhm/gdal-ruby.png)](http://travis-ci.org/zhm/gdal-ruby) +# gdal-ruby -Native bindings for GDAL/OGR for ruby. The GDAL repository contains ruby bindings -in the `swig/ruby` directory, but they aren't compiled or installed with default -installations of GDAL. In addition to not typically being installed, the GDAL build -system places the files in a global ruby directory which can cause some problems since -it's not the way other ruby libraries are typically installed. This gem simply turns -the ruby bindings from the GDAL repo into a gem which can be easily managed like all -of the other dependencies in your application. This simplifies the process of being -able to switch between versions of ruby and use bundler to manage the extension. Also -this gem enables you to install GDAL from the standard package managers that don't -include the ruby bindings. +[![CI](https://github.com/fulcrumapp/gdal-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/fulcrumapp/gdal-ruby/actions/workflows/ci.yml) -## Installation +Fulcrum’s internal fork of native GDAL/OGR bindings for Ruby. -You will first need to install GDAL. There are several ways to install it, but the -easiest way is using a package manager. +> **Status:** maintenance / compatibility shim. Upstream OSGeo GDAL no longer +> ships Ruby SWIG bindings. This gem vendors historical SWIG output and links +> against the system `libgdal`. For new shapefile import/export prefer +> [`rgeo-shapefile`](https://github.com/rgeo/rgeo-shapefile) (Fulcrum already +> uses `rgeo`). Keeping this gem is about API continuity, not greenfield design. -OS X: +## Why this is hard - brew install gdal +| Problem | Detail | +|--------|--------| +| Abandoned upstream Ruby bindings | No supported regenerate path from current GDAL | +| Native extension + system GDAL | Build needs matching headers/`gdal-config` per arch | +| Multi-arch | x86_64 and arm64 each need a successful compile against arch-native `libgdal` | +| Oversized surface | ~51k lines of generated C/C++ for a tiny Fulcrum call site | -Ubuntu: +Publishing to GitHub Packages does **not** by itself solve multi-arch: the +default artifact is a **source gem** that still compiles on install. Prebuilt +platform gems are a separate, costly project (`rake-compiler-dock`). - sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable - sudo apt-get update - sudo apt-get install libgdal-dev +## Requirements -Add this line to your application's Gemfile: +- Ruby `>= 3.1` +- System GDAL development package (`libgdal-dev` / Homebrew `gdal`) +- `pkg-config` and a C++ toolchain - gem 'gdal' +Verified CI targets: Ubuntu 24.04 amd64 + arm64, macOS 14 arm64, Ruby 3.2/3.3. -And then execute: +## Install (GitHub Packages) - $ bundle +This gem is published to the Fulcrum GitHub Packages RubyGems registry on +release (merge to `main` via release-please). -Or install it yourself as: +Bundler (`~/.bundle/config` or CI env): - $ gem install gdal - -Installing on Mac using The Framework way GDAL : - -- Download and install GDAL Complete dmg from [kyngchaos](http://www.kyngchaos.com/software/frameworks). +```bash +bundle config set --global rubygems.pkg.github.com fulcrumapp:TOKEN +# or: BUNDLE_RUBYGEMS__PKG__GITHUB__COM=fulcrumapp:${GITHUB_TOKEN} +``` -- Export path ` export PATH=/Library/Frameworks/GDAL.framework/Programs:$PATH ` in your bash or zsh profile +```ruby +# Gemfile +source "https://rubygems.pkg.github.com/fulcrumapp" do + gem "gdal", "~> 3.1" +end +``` -- Verify the installation ` gdal-config --version ` +System GDAL must still be present at `bundle install` / extension compile time. -- Install the gem using: - - ` $ gem install gdal -- --with-gdal-lib=/Library/Frameworks/GDAL.framework/unix/lib --with-gdal-includes=/Library/Frameworks/GDAL.framework/Versions/Current/Headers/ ` +### Local / source install +```bash +# macOS +brew install gdal + +# Debian/Ubuntu +sudo apt-get install -y libgdal-dev gdal-bin gdal-data pkg-config + +bundle install +bundle exec rake spec +``` ## Usage -To test it out: +```ruby +require "gdal-ruby/ogr" + +puts Gdal::Ogr + .create_geometry_from_wkt("POINT (30 10)") + .export_to_json +``` + +Fulcrum production usage today is essentially shapefile open → read fields → +`export_to_json` in `Import::Formats::Shapefile`. + +## Release automation + +On every merge to `main`: + +1. [release-please](https://github.com/googleapis/release-please) opens/updates a + Release PR from conventional commits (`feat:`, `fix:`, `chore:` …). +2. When that Release PR merges, Actions: + - tags `vX.Y.Z` + - builds the gem + - pushes to `https://rubygems.pkg.github.com/fulcrumapp` + - attaches the `.gem` to the GitHub Release + +Version source of truth: `lib/gdal-ruby/version.rb`. - $ ruby -e "require 'gdal-ruby/ogr'; puts Gdal::Ogr.create_geometry_from_wkt('POINT (30 10)').export_to_json" +## Recommended long-term direction -The best documentation for right now is the [autotest](http://trac.osgeo.org/gdal/browser/trunk/autotest/ruby/ogr) code in the GDAL source tree. You can see various -patterns for accessing files and using the OGR API from the autotest sources. +1. **Preferred:** remove this dependency from Fulcrum; implement shapefile import + with `rgeo-shapefile` + existing RGeo stack (true multi-arch, no SWIG). +2. **If GDAL formats beyond shapefile are required:** shell out to `ogr2ogr` + (`gdal-bin` already in Fulcrum images) or evaluate `ffi-gdal`. +3. **Do not** invest in full SWIG regeneration against modern GDAL without a + strong second consumer. ## License -This gem is BSD. The .c and .cpp files in the ext/gdal-ruby directory are from GDAL. For more info, -See `ext/gdal-ruby/LICENSE` or visit [gdal.org](http://www.gdal.org/). +BSD. Generated sources under `ext/gdal-ruby` come from GDAL; see +`ext/gdal-ruby/LICENSE`. diff --git a/Rakefile b/Rakefile index 04a19e0..5baa7d3 100644 --- a/Rakefile +++ b/Rakefile @@ -31,8 +31,9 @@ Rake::ExtensionTask.new('gdal-ruby/gdalconst') do |ext| end RSpec::Core::RakeTask.new(:spec) do |spec| - Rake::Task['compile'].invoke - spec.pattern = FileList['spec/**/*_spec.rb'] + Rake::Task["compile"].invoke + spec.pattern = FileList["spec/**/*_spec.rb"] + spec.rspec_opts = "--require spec_helper --format documentation" end -task :default => :spec +task default: :spec diff --git a/ext/gdal-ruby/extconf_helper.rb b/ext/gdal-ruby/extconf_helper.rb new file mode 100644 index 0000000..367baa3 --- /dev/null +++ b/ext/gdal-ruby/extconf_helper.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "mkmf" +require "shellwords" + +require_relative "ruby-2.2-patch" + +module Gdal + module ExtconfHelper + module_function + + def configure!(target:) + gdal_config = find_executable("gdal-config") + raise "gdal-config not found. Install libgdal-dev / gdal and ensure it is on PATH." unless gdal_config + + version = `#{gdal_config} --version`.strip + cflags = Shellwords.split(`#{gdal_config} --cflags`.strip) + libs = Shellwords.split(`#{gdal_config} --libs`.strip) + + incdirs = cflags.select { |f| f.start_with?("-I") }.map { |f| f.delete_prefix("-I") } + libdirs = libs.select { |f| f.start_with?("-L") }.map { |f| f.delete_prefix("-L") } + dir_config("gdal", incdirs.first, libdirs.first) + + $INCFLAGS = [incdirs.map { |d| "-I#{d}" }.join(" "), $INCFLAGS].compact.join(" ").strip + $LDFLAGS = [libdirs.map { |d| "-L#{d}" }.join(" "), $LDFLAGS].compact.join(" ").strip + + pkg_config("gdal") + have_library("gdal") or raise "libgdal not found (gdal-config reported #{version})" + $libs = append_library($libs, "gdal") + + $CXXFLAGS = CONFIG["CXXFLAGS"] unless defined?($CXXFLAGS) && $CXXFLAGS + + common_warnings = " -Wno-format-security -Wno-unused-result -Wno-deprecated-declarations" + $CFLAGS << common_warnings + $CXXFLAGS << common_warnings + + major = version.split(".").first.to_i + # Only C++ extensions need a modern dialect; gdalconst is pure C. + if major >= 2 && target != "gdal-ruby/gdalconst" + $CXXFLAGS << " -Wno-reserved-user-defined-literal -std=c++17" + end + + libdirs.each do |dir| + $LDFLAGS << " -Wl,-rpath,#{dir}" if RUBY_PLATFORM.include?("darwin") + end + + puts "Using GDAL #{version} for #{target}" + create_makefile(target) + end + end +end diff --git a/ext/gdal-ruby/gdal/extconf.rb b/ext/gdal-ruby/gdal/extconf.rb index 18f0ed3..a74c9c7 100644 --- a/ext/gdal-ruby/gdal/extconf.rb +++ b/ext/gdal-ruby/gdal/extconf.rb @@ -1,30 +1,5 @@ -require 'mkmf' +# frozen_string_literal: true -# see https://github.com/zhm/gdal-ruby/issues/5 -require_relative '../ruby-2.2-patch' - -raise 'gdal-config not found.' if `which gdal-config`.empty? - -dir_config 'gdal', - `gdal-config --cflags`.split(' ')[0].gsub(/-I/, ''), - `gdal-config --libs`.split(' ')[0].gsub(/-L/, '') - -have_library 'gdal' or raise 'libgdal not found' - -pkg_config 'gdal' - -$libs = append_library $libs, 'gdal' - -# earlier versions of ruby do not define $CXXFLAGS -$CXXFLAGS = CONFIG["CXXFLAGS"] unless defined?($CXXFLAGS) - -$CFLAGS << ' -Wno-format-security' -$CXXFLAGS << ' -Wno-format-security' - -if !(`gdal-config --version`.strip =~ /^1/) - $CFLAGS << ' -Wno-reserved-user-defined-literal -std=c++11' - $CXXFLAGS << ' -Wno-reserved-user-defined-literal -std=c++11' -end - -create_makefile 'gdal-ruby/gdal' +require_relative "../extconf_helper" +Gdal::ExtconfHelper.configure!(target: "gdal-ruby/gdal") diff --git a/ext/gdal-ruby/gdal/gdal.cpp b/ext/gdal-ruby/gdal/gdal.cpp index fa42c63..fa9bd00 100644 --- a/ext/gdal-ruby/gdal/gdal.cpp +++ b/ext/gdal-ruby/gdal/gdal.cpp @@ -1504,13 +1504,16 @@ SWIG_Ruby_InitRuntime(void) SWIGRUNTIME void SWIG_Ruby_define_class(swig_type_info *type) { + VALUE klass; char *klass_name = (char *) malloc(4 + strlen(type->name) + 1); sprintf(klass_name, "TYPE%s", type->name); if (NIL_P(_cSWIG_Pointer)) { _cSWIG_Pointer = rb_define_class_under(_mSWIG, "Pointer", rb_cObject); - rb_undef_method(CLASS_OF(_cSWIG_Pointer), "new"); + /* Ruby 3.2+: avoid "undefining the allocator of T_DATA class" warnings */ + rb_undef_alloc_func(_cSWIG_Pointer); } - rb_define_class_under(_mSWIG, klass_name, _cSWIG_Pointer); + klass = rb_define_class_under(_mSWIG, klass_name, _cSWIG_Pointer); + rb_undef_alloc_func(klass); free((void *) klass_name); } @@ -1748,6 +1751,8 @@ SWIG_Ruby_SetModule(swig_module_info *pointer) { /* register a new class */ VALUE cl = rb_define_class("swig_runtime_data", rb_cObject); + /* Ruby 3.2+: undef default allocator before first Data_Wrap_Struct */ + rb_undef_alloc_func(cl); /* create and store the structure pointer to a global variable */ swig_runtime_data_type_pointer = Data_Wrap_Struct(cl, 0, 0, pointer); rb_define_readonly_variable("$swig_runtime_data_type_pointer" SWIG_RUNTIME_VERSION SWIG_TYPE_TABLE_NAME, &swig_runtime_data_type_pointer); @@ -2170,10 +2175,10 @@ SWIGINTERN void GDALMajorObjectShadow_SetDescription(GDALMajorObjectShadow *self GDALSetDescription( self, pszNewDesc ); } SWIGINTERN char **GDALMajorObjectShadow_GetMetadata_Dict(GDALMajorObjectShadow *self,char const *pszDomain=""){ - return GDALGetMetadata( self, pszDomain ); + return const_cast(GDALGetMetadata(self, pszDomain )); } SWIGINTERN char **GDALMajorObjectShadow_GetMetadata_List(GDALMajorObjectShadow *self,char const *pszDomain=""){ - return GDALGetMetadata( self, pszDomain ); + return const_cast(GDALGetMetadata(self, pszDomain )); } SWIGINTERN CPLErr GDALMajorObjectShadow_SetMetadata__SWIG_0(GDALMajorObjectShadow *self,char **papszMetadata,char const *pszDomain=""){ return GDALSetMetadata( self, papszMetadata, pszDomain ); diff --git a/ext/gdal-ruby/gdalconst/extconf.rb b/ext/gdal-ruby/gdalconst/extconf.rb index beb94d4..0110d89 100644 --- a/ext/gdal-ruby/gdalconst/extconf.rb +++ b/ext/gdal-ruby/gdalconst/extconf.rb @@ -1,24 +1,5 @@ -require 'mkmf' +# frozen_string_literal: true -# see https://github.com/zhm/gdal-ruby/issues/5 -require_relative '../ruby-2.2-patch' +require_relative "../extconf_helper" -raise 'gdal-config not found.' if `which gdal-config`.empty? - -dir_config 'gdal', - `gdal-config --cflags`.split(' ')[0].gsub(/-I/, ''), - `gdal-config --libs`.split(' ')[0].gsub(/-L/, '') - -have_library 'gdal' or raise 'libgdal not found' - -pkg_config 'gdal' - -$libs = append_library $libs, 'gdal' - -# earlier versions of ruby do not define $CXXFLAGS -$CXXFLAGS = CONFIG["CXXFLAGS"] unless defined?($CXXFLAGS) - -$CFLAGS << ' -Wno-format-security' -$CXXFLAGS << ' -Wno-format-security' - -create_makefile('gdal-ruby/gdalconst') +Gdal::ExtconfHelper.configure!(target: "gdal-ruby/gdalconst") diff --git a/ext/gdal-ruby/gdalconst/gdalconst.c b/ext/gdal-ruby/gdalconst/gdalconst.c index 81090ff..095faca 100644 --- a/ext/gdal-ruby/gdalconst/gdalconst.c +++ b/ext/gdal-ruby/gdalconst/gdalconst.c @@ -1481,13 +1481,16 @@ SWIG_Ruby_InitRuntime(void) SWIGRUNTIME void SWIG_Ruby_define_class(swig_type_info *type) { + VALUE klass; char *klass_name = (char *) malloc(4 + strlen(type->name) + 1); sprintf(klass_name, "TYPE%s", type->name); if (NIL_P(_cSWIG_Pointer)) { _cSWIG_Pointer = rb_define_class_under(_mSWIG, "Pointer", rb_cObject); - rb_undef_method(CLASS_OF(_cSWIG_Pointer), "new"); + /* Ruby 3.2+: avoid "undefining the allocator of T_DATA class" warnings */ + rb_undef_alloc_func(_cSWIG_Pointer); } - rb_define_class_under(_mSWIG, klass_name, _cSWIG_Pointer); + klass = rb_define_class_under(_mSWIG, klass_name, _cSWIG_Pointer); + rb_undef_alloc_func(klass); free((void *) klass_name); } @@ -1725,6 +1728,8 @@ SWIG_Ruby_SetModule(swig_module_info *pointer) { /* register a new class */ VALUE cl = rb_define_class("swig_runtime_data", rb_cObject); + /* Ruby 3.2+: undef default allocator before first Data_Wrap_Struct */ + rb_undef_alloc_func(cl); /* create and store the structure pointer to a global variable */ swig_runtime_data_type_pointer = Data_Wrap_Struct(cl, 0, 0, pointer); rb_define_readonly_variable("$swig_runtime_data_type_pointer" SWIG_RUNTIME_VERSION SWIG_TYPE_TABLE_NAME, &swig_runtime_data_type_pointer); diff --git a/ext/gdal-ruby/ogr/extconf.rb b/ext/gdal-ruby/ogr/extconf.rb index 3460654..4f95d59 100644 --- a/ext/gdal-ruby/ogr/extconf.rb +++ b/ext/gdal-ruby/ogr/extconf.rb @@ -1,29 +1,5 @@ -require 'mkmf' +# frozen_string_literal: true -# see https://github.com/zhm/gdal-ruby/issues/5 -require_relative '../ruby-2.2-patch' +require_relative "../extconf_helper" -raise 'gdal-config not found.' if `which gdal-config`.empty? - -dir_config 'gdal', - `gdal-config --cflags`.split(' ')[0].gsub(/-I/, ''), - `gdal-config --libs`.split(' ')[0].gsub(/-L/, '') - -have_library 'gdal' or raise 'libgdal not found' - -pkg_config 'gdal' - -$libs = append_library $libs, 'gdal' - -# earlier versions of ruby do not define $CXXFLAGS -$CXXFLAGS = CONFIG["CXXFLAGS"] unless defined?($CXXFLAGS) - -$CFLAGS << ' -Wno-format-security' -$CXXFLAGS << ' -Wno-format-security' - -if !(`gdal-config --version`.strip =~ /^1/) - $CFLAGS << ' -Wno-reserved-user-defined-literal -std=c++11' - $CXXFLAGS << ' -Wno-reserved-user-defined-literal -std=c++11' -end - -create_makefile('gdal-ruby/ogr') +Gdal::ExtconfHelper.configure!(target: "gdal-ruby/ogr") diff --git a/ext/gdal-ruby/ogr/ogr.cpp b/ext/gdal-ruby/ogr/ogr.cpp index fcfdd47..7715a0c 100644 --- a/ext/gdal-ruby/ogr/ogr.cpp +++ b/ext/gdal-ruby/ogr/ogr.cpp @@ -1504,13 +1504,16 @@ SWIG_Ruby_InitRuntime(void) SWIGRUNTIME void SWIG_Ruby_define_class(swig_type_info *type) { + VALUE klass; char *klass_name = (char *) malloc(4 + strlen(type->name) + 1); sprintf(klass_name, "TYPE%s", type->name); if (NIL_P(_cSWIG_Pointer)) { _cSWIG_Pointer = rb_define_class_under(_mSWIG, "Pointer", rb_cObject); - rb_undef_method(CLASS_OF(_cSWIG_Pointer), "new"); + /* Ruby 3.2+: avoid "undefining the allocator of T_DATA class" warnings */ + rb_undef_alloc_func(_cSWIG_Pointer); } - rb_define_class_under(_mSWIG, klass_name, _cSWIG_Pointer); + klass = rb_define_class_under(_mSWIG, klass_name, _cSWIG_Pointer); + rb_undef_alloc_func(klass); free((void *) klass_name); } @@ -1748,6 +1751,8 @@ SWIG_Ruby_SetModule(swig_module_info *pointer) { /* register a new class */ VALUE cl = rb_define_class("swig_runtime_data", rb_cObject); + /* Ruby 3.2+: undef default allocator before first Data_Wrap_Struct */ + rb_undef_alloc_func(cl); /* create and store the structure pointer to a global variable */ swig_runtime_data_type_pointer = Data_Wrap_Struct(cl, 0, 0, pointer); rb_define_readonly_variable("$swig_runtime_data_type_pointer" SWIG_RUNTIME_VERSION SWIG_TYPE_TABLE_NAME, &swig_runtime_data_type_pointer); diff --git a/ext/gdal-ruby/osr/extconf.rb b/ext/gdal-ruby/osr/extconf.rb index 46c7ef4..559acc8 100644 --- a/ext/gdal-ruby/osr/extconf.rb +++ b/ext/gdal-ruby/osr/extconf.rb @@ -1,29 +1,5 @@ -require 'mkmf' +# frozen_string_literal: true -# see https://github.com/zhm/gdal-ruby/issues/5 -require_relative '../ruby-2.2-patch' +require_relative "../extconf_helper" -raise 'gdal-config not found.' if `which gdal-config`.empty? - -dir_config 'gdal', - `gdal-config --cflags`.split(' ')[0].gsub(/-I/, ''), - `gdal-config --libs`.split(' ')[0].gsub(/-L/, '') - -have_library 'gdal' or raise 'libgdal not found' - -pkg_config 'gdal' - -$libs = append_library $libs, 'gdal' - -# earlier versions of ruby do not define $CXXFLAGS -$CXXFLAGS = CONFIG["CXXFLAGS"] unless defined?($CXXFLAGS) - -$CFLAGS << ' -Wno-format-security' -$CXXFLAGS << ' -Wno-format-security' - -if !(`gdal-config --version`.strip =~ /^1/) - $CFLAGS << ' -Wno-reserved-user-defined-literal -std=c++11' - $CXXFLAGS << ' -Wno-reserved-user-defined-literal -std=c++11' -end - -create_makefile('gdal-ruby/osr') +Gdal::ExtconfHelper.configure!(target: "gdal-ruby/osr") diff --git a/ext/gdal-ruby/osr/osr.cpp b/ext/gdal-ruby/osr/osr.cpp index b170eea..eceb3f2 100644 --- a/ext/gdal-ruby/osr/osr.cpp +++ b/ext/gdal-ruby/osr/osr.cpp @@ -10,6 +10,10 @@ #include "cpl_port.h" + +#ifndef ABS +#define ABS(x) ((x) < 0 ? -(x) : (x)) +#endif #define SWIGRUBY #ifdef __cplusplus @@ -1504,13 +1508,16 @@ SWIG_Ruby_InitRuntime(void) SWIGRUNTIME void SWIG_Ruby_define_class(swig_type_info *type) { + VALUE klass; char *klass_name = (char *) malloc(4 + strlen(type->name) + 1); sprintf(klass_name, "TYPE%s", type->name); if (NIL_P(_cSWIG_Pointer)) { _cSWIG_Pointer = rb_define_class_under(_mSWIG, "Pointer", rb_cObject); - rb_undef_method(CLASS_OF(_cSWIG_Pointer), "new"); + /* Ruby 3.2+: avoid "undefining the allocator of T_DATA class" warnings */ + rb_undef_alloc_func(_cSWIG_Pointer); } - rb_define_class_under(_mSWIG, klass_name, _cSWIG_Pointer); + klass = rb_define_class_under(_mSWIG, klass_name, _cSWIG_Pointer); + rb_undef_alloc_func(klass); free((void *) klass_name); } @@ -1748,6 +1755,8 @@ SWIG_Ruby_SetModule(swig_module_info *pointer) { /* register a new class */ VALUE cl = rb_define_class("swig_runtime_data", rb_cObject); + /* Ruby 3.2+: undef default allocator before first Data_Wrap_Struct */ + rb_undef_alloc_func(cl); /* create and store the structure pointer to a global variable */ swig_runtime_data_type_pointer = Data_Wrap_Struct(cl, 0, 0, pointer); rb_define_readonly_variable("$swig_runtime_data_type_pointer" SWIG_RUNTIME_VERSION SWIG_TYPE_TABLE_NAME, &swig_runtime_data_type_pointer); @@ -2214,7 +2223,7 @@ SWIGINTERN int OSRSpatialReferenceShadow_GetUTMZone(OSRSpatialReferenceShadow *s int bNorth = FALSE; int nZone = OSRGetUTMZone( self, &bNorth ); if( !bNorth ) - nZone = -1 * ABS(nZone); + nZone = -1 * ((nZone) < 0 ? -(nZone) : (nZone)); return nZone; } SWIGINTERN OGRErr OSRSpatialReferenceShadow_SetStatePlane(OSRSpatialReferenceShadow *self,int zone,int is_nad83=1,char const *unitsname="",double units=0.0){ diff --git a/gdal.gemspec b/gdal.gemspec index 72d2a3c..9b58262 100644 --- a/gdal.gemspec +++ b/gdal.gemspec @@ -1,23 +1,46 @@ -# -*- encoding: utf-8 -*- -require File.expand_path('../lib/gdal-ruby/version', __FILE__) +# frozen_string_literal: true + +require_relative "lib/gdal-ruby/version" Gem::Specification.new do |gem| - gem.authors = ["Zac McCormick"] + gem.name = "gdal" + gem.version = Gdal::Ruby::VERSION + gem.authors = ["Zac McCormick", "Fulcrum"] gem.email = ["zac.mccormick@gmail.com"] - gem.description = %q{GDAL/OGR bindings for ruby} - gem.summary = %q{GDAL/OGR bindings for ruby. Currently contains native extensions for GDAL 1.9.1} - gem.homepage = "https://github.com/zhm/gdal-ruby" + gem.summary = "GDAL/OGR bindings for Ruby (Fulcrum internal fork)" + gem.description = <<~DESC + Native GDAL/OGR bindings packaged as a gem. This is the Fulcrum-maintained + fork of zhm/gdal-ruby. Bindings are historical SWIG output and are not kept + in lockstep with the latest GDAL API. Prefer rgeo-shapefile for new + shapefile work when full GDAL is not required. + DESC + gem.homepage = "https://github.com/fulcrumapp/gdal-ruby" + gem.license = "BSD-3-Clause" + gem.required_ruby_version = ">= 3.1.0" - gem.files = `git ls-files`.split($\) - gem.extensions = ['ext/gdal-ruby/gdal/extconf.rb', 'ext/gdal-ruby/gdalconst/extconf.rb', - 'ext/gdal-ruby/ogr/extconf.rb', 'ext/gdal-ruby/osr/extconf.rb'] - gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } - gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) - gem.name = "gdal" + gem.metadata = { + "bug_tracker_uri" => "https://github.com/fulcrumapp/gdal-ruby/issues", + "changelog_uri" => "https://github.com/fulcrumapp/gdal-ruby/blob/main/CHANGELOG.md", + "homepage_uri" => gem.homepage, + "source_code_uri" => "https://github.com/fulcrumapp/gdal-ruby", + "github_repo" => "ssh://github.com/fulcrumapp/gdal-ruby", + "allowed_push_host" => "https://rubygems.pkg.github.com/fulcrumapp" + } + + gem.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject do |f| + f.start_with?("spec/", "test/", ".github/", ".travis.yml") + end + end gem.require_paths = ["lib"] - gem.version = Gdal::Ruby::VERSION + gem.extensions = [ + "ext/gdal-ruby/gdal/extconf.rb", + "ext/gdal-ruby/gdalconst/extconf.rb", + "ext/gdal-ruby/ogr/extconf.rb", + "ext/gdal-ruby/osr/extconf.rb" + ] - gem.add_development_dependency 'rake', ['>= 0'] - gem.add_development_dependency 'rake-compiler', ['>= 0'] - gem.add_development_dependency 'rspec', ['>= 0'] + gem.add_development_dependency "rake", "~> 13.0" + gem.add_development_dependency "rake-compiler", "~> 1.2" + gem.add_development_dependency "rspec", "~> 3.13" end diff --git a/lib/gdal-ruby/version.rb b/lib/gdal-ruby/version.rb index b99deeb..b6f1a2e 100644 --- a/lib/gdal-ruby/version.rb +++ b/lib/gdal-ruby/version.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true + module Gdal module Ruby - VERSION = "2.0.0" + # Keep in sync with Git tags (vX.Y.Z) and GitHub Packages releases. + # Fulcrum previously locked RubyGems 3.0.0 while this file still said 2.0.0. + VERSION = "3.1.0" end end diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..5fd7c48 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,12 @@ +{ + "packages": { + ".": { + "release-type": "ruby", + "package-name": "gdal", + "version-file": "lib/gdal-ruby/version.rb", + "changelog-path": "CHANGELOG.md", + "bump-minor-pre-major": true, + "include-component-in-tag": false + } + } +} diff --git a/spec/fixtures/shapefiles/flat/points.dbf b/spec/fixtures/shapefiles/flat/points.dbf new file mode 100644 index 0000000000000000000000000000000000000000..82a82fb2a6df121725d296759174dbe767092db3 GIT binary patch literal 550 zcmZRsW0z%OU|?9ta0p1Efp7+fyu{p8sHiiD4nR|qoL`z(0ul2AQJiS<#mV_aPy@j7 z68xwN^72dYn$N3{m{X9Es6c&S3^meF&(r_~Bqyf5q^fmEsU@`VfT01@l}36-s9q?@ LNzBZnq8Fq9QNcui literal 0 HcmV?d00001 diff --git a/spec/fixtures/shapefiles/flat/points.prj b/spec/fixtures/shapefiles/flat/points.prj new file mode 100644 index 0000000..8f73f48 --- /dev/null +++ b/spec/fixtures/shapefiles/flat/points.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] diff --git a/spec/fixtures/shapefiles/flat/points.shp b/spec/fixtures/shapefiles/flat/points.shp new file mode 100644 index 0000000000000000000000000000000000000000..5ef5f89753316d7ea2a0f9d64dbbc6ddd58007d2 GIT binary patch literal 172 zcmZQzQ0HR64#HkAGca(&CobuqY#t cCobu&5D` KY{ literal 0 HcmV?d00001 diff --git a/spec/fixtures/shapefiles/lines/lines.dbf b/spec/fixtures/shapefiles/lines/lines.dbf new file mode 100644 index 0000000000000000000000000000000000000000..505dc6f4c80273737923eb93b1c2c047aeba0c06 GIT binary patch literal 356 zcmZRsW0z%QU|?9ta0p1Efp7+fyu{p8sHiiD4nR|qoL`z(0ul2AQJiS<#mV_aPy@j7 q68xwN^72dYn$N3HlwVqss+*`lZD0sB&_K`B00rb^=A}~8GARJz2Pl64 literal 0 HcmV?d00001 diff --git a/spec/fixtures/shapefiles/lines/lines.prj b/spec/fixtures/shapefiles/lines/lines.prj new file mode 100644 index 0000000..f45cbad --- /dev/null +++ b/spec/fixtures/shapefiles/lines/lines.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] \ No newline at end of file diff --git a/spec/fixtures/shapefiles/lines/lines.shp b/spec/fixtures/shapefiles/lines/lines.shp new file mode 100644 index 0000000000000000000000000000000000000000..e83d6996f7ab3b53c5e15d8afdba6b445262035a GIT binary patch literal 204 zcmZQzQ0HR64$@vQGcW)VgDuzf#RYRi4j6;LXKRO;{zG3i=7b#hrU7DEJ7Cw2BFYHl a84zO@NFhia2%y`0_6!gp*$ZYlR6=KX{ P0xE==53>(uKg>P=lYT7< literal 0 HcmV?d00001 diff --git a/spec/fixtures/shapefiles/multipoint/multipoint.shx b/spec/fixtures/shapefiles/multipoint/multipoint.shx new file mode 100644 index 0000000000000000000000000000000000000000..6e173f6292f026570bbd738baded79e9a3a6186c GIT binary patch literal 108 zcmZQzQ0HR64$NLKGca%f<<6b~f}W5Avt|N;q_smD2(*P9Fa`r_2khEWM2&zv6#&(| B5G4Qr literal 0 HcmV?d00001 diff --git a/spec/fixtures/shapefiles/points/points.dbf b/spec/fixtures/shapefiles/points/points.dbf new file mode 100644 index 0000000000000000000000000000000000000000..82a82fb2a6df121725d296759174dbe767092db3 GIT binary patch literal 550 zcmZRsW0z%OU|?9ta0p1Efp7+fyu{p8sHiiD4nR|qoL`z(0ul2AQJiS<#mV_aPy@j7 z68xwN^72dYn$N3{m{X9Es6c&S3^meF&(r_~Bqyf5q^fmEsU@`VfT01@l}36-s9q?@ LNzBZnq8Fq9QNcui literal 0 HcmV?d00001 diff --git a/spec/fixtures/shapefiles/points/points.prj b/spec/fixtures/shapefiles/points/points.prj new file mode 100644 index 0000000..af6a6b1 --- /dev/null +++ b/spec/fixtures/shapefiles/points/points.prj @@ -0,0 +1 @@ +GEOGCS["WGS_1984_3D",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433],LINUNIT["Meter",1.0]] \ No newline at end of file diff --git a/spec/fixtures/shapefiles/points/points.shp b/spec/fixtures/shapefiles/points/points.shp new file mode 100644 index 0000000000000000000000000000000000000000..5ef5f89753316d7ea2a0f9d64dbbc6ddd58007d2 GIT binary patch literal 172 zcmZQzQ0HR64#HkAGca(&CobuqY#t cCobu&5D` KY{ literal 0 HcmV?d00001 diff --git a/spec/fixtures/shapefiles/polygons/polygons.dbf b/spec/fixtures/shapefiles/polygons/polygons.dbf new file mode 100644 index 0000000000000000000000000000000000000000..df7e2395d1b953c46e762373a6518e03b305c943 GIT binary patch literal 356 zcmZRsW0z%QU|?9ta0p1Efp7+fyu{p8sHiiD4nR|qoL`z(0ul2AQJiS<#mV_aPy@j7 n68xwN^72dYn$N3{l#`#Ftw4QX1U1r9&j1w^f^tmo6wS literal 0 HcmV?d00001 diff --git a/spec/fixtures/shapefiles/polygons/polygons.prj b/spec/fixtures/shapefiles/polygons/polygons.prj new file mode 100644 index 0000000..f45cbad --- /dev/null +++ b/spec/fixtures/shapefiles/polygons/polygons.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] \ No newline at end of file diff --git a/spec/fixtures/shapefiles/polygons/polygons.shp b/spec/fixtures/shapefiles/polygons/polygons.shp new file mode 100644 index 0000000000000000000000000000000000000000..733af4cf8955654ee3d9a2634f1fa7f93229de8f GIT binary patch literal 236 zcmZQzQ0HR64$59IGcd3Mi0MD{)!G5Ob`()Y fAkTpqvp@<#>OcV9UKk%{FSS25U9RW!X literal 0 HcmV?d00001 diff --git a/spec/fixtures/shapefiles/polygons/polygons.shx b/spec/fixtures/shapefiles/polygons/polygons.shx new file mode 100644 index 0000000000000000000000000000000000000000..73a1c84bfb2092b711b70e46b606c9c4eccbf551 GIT binary patch literal 108 zcmZQzQ0HR64$NLKGcd3Mi0MD{)!G5Ob`((~ HAkP5+^o0@u literal 0 HcmV?d00001 diff --git a/spec/fulcrum_shapefile_import_spec.rb b/spec/fulcrum_shapefile_import_spec.rb new file mode 100644 index 0000000..ae51445 --- /dev/null +++ b/spec/fulcrum_shapefile_import_spec.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +require "spec_helper" +require_relative "support/fulcrum_shapefile_importer" + +RSpec.describe "Fulcrum Import::Formats::Shapefile surface" do + def shp(name) + FIXTURES.join(name, "#{File.basename(name)}.shp") + end + + describe "constants used by Fulcrum schema mapping" do + it "exposes OGR field type constants" do + expect(Gdal::Ogr::OFTSTRING).to be_a(Integer) + expect(Gdal::Ogr::OFTINTEGER).to be_a(Integer) + expect(Gdal::Ogr::OFTREAL).to be_a(Integer) + expect(Gdal::Ogr::OFTSTRING).not_to eq(Gdal::Ogr::OFTINTEGER) + expect(Gdal::Ogr::OFTINTEGER).not_to eq(Gdal::Ogr::OFTREAL) + end + + it "exposes WKB geometry constants referenced by Fulcrum" do + %i[ + WKBPOINT WKBPOINT25D WKBMULTIPOINT WKBMULTIPOINT25D + WKBLINESTRING WKBMULTILINESTRING WKBLINEARRING WKBLINESTRING25D WKBMULTILINESTRING25D + WKBPOLYGON WKBMULTIPOLYGON WKBPOLYGON25D WKBMULTIPOLYGON25D + ].each do |name| + expect(Gdal::Ogr.const_get(name)).to be_a(Integer), "missing #{name}" + end + end + end + + describe "open + layer access" do + it "opens a .shp path like Fulcrum file_path usage" do + ds = FulcrumShapefileImporter.open(FIXTURES.join("flat/points.shp")) + expect(ds).not_to be_nil + expect(FulcrumShapefileImporter.layer(ds).get_feature_count).to eq(2) + end + + it "opens a directory datasource containing a shapefile" do + ds = FulcrumShapefileImporter.open(FIXTURES.join("points")) + expect(FulcrumShapefileImporter.feature_count(FulcrumShapefileImporter.layer(ds))).to eq(2) + end + end + + describe "points fixture (primary Fulcrum path)" do + let(:path) { shp("points") } + let(:ds) { FulcrumShapefileImporter.open(path) } + let(:layer) { FulcrumShapefileImporter.layer(ds) } + + it "reports feature_count" do + expect(FulcrumShapefileImporter.feature_count(layer)).to eq(2) + end + + it "maps geometry kind to :point" do + expect(FulcrumShapefileImporter.geometry_kind(layer)).to eq(:point) + end + + it "loads field schema with string/integer/real types" do + columns = FulcrumShapefileImporter.schema_columns(layer) + by_name = columns.to_h { |c| [c[:name], c[:type]] } + + expect(by_name).to include( + "name" => :string, + "count" => :integer, + "score" => :double, + "note" => :string + ) + end + + it "reads attributes with the same field accessors Fulcrum uses" do + rows = FulcrumShapefileImporter.each_feature(path) + expect(rows.size).to eq(2) + + first = rows[0] + expect(first["name"]).to eq("alpha") + expect(first["count"]).to eq(3) + expect(first["score"]).to eq(1.5) + expect(first["note"]).to eq("café") + end + + it "exports geometry to GeoJSON after flatten_to_2d" do + rows = FulcrumShapefileImporter.each_feature(path) + geom = rows[0]["__geometry__"] + + expect(geom["type"]).to eq("Point") + expect(geom["coordinates"].size).to eq(2) + expect(geom["coordinates"][0]).to be_within(0.0001).of(-82.4572) + expect(geom["coordinates"][1]).to be_within(0.0001).of(27.9506) + end + + it "flattens 3D points to 2 coordinates for GeoUtils consumers" do + rows = FulcrumShapefileImporter.each_feature(path) + three_d = rows.find { |r| r["name"] == "beta" } + expect(three_d["__geometry__"]["coordinates"].size).to eq(2) + end + + it "force-encodes string fields as UTF-8 like Fulcrum text_value" do + rows = FulcrumShapefileImporter.each_feature(path) + note = rows[0]["note"] + expect(note.encoding).to eq(Encoding::UTF_8) + expect(note).to eq("café") + end + + it "supports random access get_feature by cursor index" do + second = FulcrumShapefileImporter.read_feature(layer, 1) + expect(second["name"]).to eq("beta") + expect(second["count"]).to eq(10) + expect(second["score"]).to eq(2.25) + end + end + + describe "lines fixture" do + let(:path) { shp("lines") } + let(:layer) { FulcrumShapefileImporter.layer(FulcrumShapefileImporter.open(path)) } + + it "maps geometry kind to :line" do + expect(FulcrumShapefileImporter.geometry_kind(layer)).to eq(:line) + end + + it "exports LineString GeoJSON" do + row = FulcrumShapefileImporter.read_feature(layer, 0) + expect(row["__geometry__"]["type"]).to eq("LineString") + expect(row["__geometry__"]["coordinates"]).to be_a(Array) + expect(row["name"]).to eq("route-a") + end + end + + describe "polygons fixture" do + let(:path) { shp("polygons") } + let(:layer) { FulcrumShapefileImporter.layer(FulcrumShapefileImporter.open(path)) } + + it "maps geometry kind to :polygon" do + expect(FulcrumShapefileImporter.geometry_kind(layer)).to eq(:polygon) + end + + it "exports Polygon GeoJSON" do + row = FulcrumShapefileImporter.read_feature(layer, 0) + expect(row["__geometry__"]["type"]).to eq("Polygon") + expect(row["__geometry__"]["coordinates"].first.size).to be >= 4 + expect(row["name"]).to eq("block") + end + end + + describe "multipoint fixture" do + let(:path) { shp("multipoint") } + let(:layer) { FulcrumShapefileImporter.layer(FulcrumShapefileImporter.open(path)) } + + it "maps multipoint to :point like Fulcrum case branches" do + expect(FulcrumShapefileImporter.geometry_kind(layer)).to eq(:point) + end + + it "exports MultiPoint GeoJSON" do + row = FulcrumShapefileImporter.read_feature(layer, 0) + expect(row["__geometry__"]["type"]).to eq("MultiPoint") + expect(row["__geometry__"]["coordinates"].size).to eq(2) + end + end + + describe "end-to-end parity with Fulcrum next_feature loop" do + it "walks every feature without raising and yields geometry + attrs" do + %w[points lines polygons multipoint].each do |name| + rows = FulcrumShapefileImporter.each_feature(shp(name)) + expect(rows).not_to be_empty + rows.each do |row| + expect(row).to have_key("__geometry__") + expect(row["__geometry__"]).to be_a(Hash) + expect(row["__geometry__"]).to have_key("type") + expect(row).to have_key("name") + end + end + end + end +end diff --git a/spec/gdal_spec.rb b/spec/gdal_spec.rb index d10b85b..4812c34 100644 --- a/spec/gdal_spec.rb +++ b/spec/gdal_spec.rb @@ -1,10 +1,22 @@ -require 'gdal-ruby/ogr' +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "Gdal" do + it "reports a version" do + expect(Gdal::Ruby::VERSION).to match(/\A\d+\.\d+\.\d+\z/) + end -describe "Gdal" do it "converts WKT to GeoJSON" do - valid_wkt = 'POINT (30 10)' - valid_json = '{ "type": "Point", "coordinates": [ 30.0, 10.0 ] }' + geometry = Gdal::Ogr.create_geometry_from_wkt("POINT (30 10)") + parsed = JSON.parse(geometry.export_to_json) + + expect(parsed).to eq("type" => "Point", "coordinates" => [30.0, 10.0]) + end - Gdal::Ogr.create_geometry_from_wkt(valid_wkt).export_to_json.should eq(valid_json) + it "loads all extension entrypoints used by require 'gdal'" do + expect(defined?(Gdal::Gdal)).to be_truthy + expect(defined?(Gdal::Ogr)).to be_truthy + expect(defined?(Gdal::Osr)).to be_truthy end end diff --git a/spec/ruby3_warnings_spec.rb b/spec/ruby3_warnings_spec.rb new file mode 100644 index 0000000..6defd04 --- /dev/null +++ b/spec/ruby3_warnings_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "spec_helper" +require "open3" + +RSpec.describe "Ruby 3 SWIG allocator warnings" do + it "does not emit T_DATA allocator warnings on require/use" do + script = <<~RUBY + Warning[:deprecated] = true if Warning.respond_to?(:[]=) + require "gdal" + g = Gdal::Ogr.create_geometry_from_wkt("POINT (1 2)") + puts g.export_to_json + ds = Gdal::Ogr.open(#{FIXTURES.join('flat/points.shp').to_s.inspect}) + layer = ds.get_layer(0) + f = layer.get_feature(0) + puts f.get_field_as_string(0) + RUBY + + env = { + "BUNDLE_GEMFILE" => File.expand_path("../Gemfile", __dir__), + "RUBYOPT" => "-W2" + } + + stdout, stderr, status = Open3.capture3(env, "bundle", "exec", "ruby", "-e", script) + combined = stdout + stderr + + expect(status.success?).to eq(true), combined + expect(combined).not_to match(/undefining the allocator of T_DATA class/) + expect(combined).not_to match(/swig_runtime_data/) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..4271bfb --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "json" +require "pathname" +require "gdal" + +FIXTURES = Pathname.new(__dir__).join("fixtures/shapefiles").expand_path + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.shared_context_metadata_behavior = :apply_to_host_groups + config.filter_run_when_matching :focus + config.example_status_persistence_file_path = "spec/examples.txt" + config.disable_monkey_patching! + config.order = :random + Kernel.srand config.seed +end diff --git a/spec/support/fulcrum_shapefile_importer.rb b/spec/support/fulcrum_shapefile_importer.rb new file mode 100644 index 0000000..df793fb --- /dev/null +++ b/spec/support/fulcrum_shapefile_importer.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +# Mirrors fulcrumapp/fulcrum app/classes/import/formats/shapefile.rb call surface +# against this gem. Keep behavior aligned when changing either side. +module FulcrumShapefileImporter + module_function + + def open(path) + Gdal::Ogr.open(path.to_s) + end + + def layer(datasource, index = 0) + datasource.get_layer(index) + end + + def feature_count(layer) + layer.get_feature_count + end + + def field_info(layer) + layer.get_layer_defn + end + + def field_count(layer) + field_info(layer).get_field_count + end + + def geometry_kind(layer) + case layer.get_geom_type + when Gdal::Ogr::WKBPOINT, + Gdal::Ogr::WKBPOINT25D, + Gdal::Ogr::WKBMULTIPOINT, + Gdal::Ogr::WKBMULTIPOINT25D + :point + when Gdal::Ogr::WKBLINESTRING, + Gdal::Ogr::WKBMULTILINESTRING, + Gdal::Ogr::WKBLINEARRING, + Gdal::Ogr::WKBLINESTRING25D, + Gdal::Ogr::WKBMULTILINESTRING25D + :line + when Gdal::Ogr::WKBPOLYGON, + Gdal::Ogr::WKBMULTIPOLYGON, + Gdal::Ogr::WKBPOLYGON25D, + Gdal::Ogr::WKBMULTIPOLYGON25D + :polygon + else + :unknown + end + end + + def schema_columns(layer) + info = field_info(layer) + field_count(layer).times.map do |index| + defn = info.get_field_defn(index) + type = + case defn.get_type + when Gdal::Ogr::OFTSTRING then :string + when Gdal::Ogr::OFTINTEGER then :integer + when Gdal::Ogr::OFTREAL then :double + else :string + end + { name: defn.get_name, type: type } + end + end + + def read_feature(layer, index) + feature = layer.get_feature(index) + info = field_info(layer) + attrs = {} + + field_count(layer).times do |field_index| + defn = info.get_field_defn(field_index) + value = + case defn.get_type + when Gdal::Ogr::OFTSTRING + text_value(feature.get_field_as_string(field_index)) + when Gdal::Ogr::OFTINTEGER + feature.get_field_as_integer(field_index) + when Gdal::Ogr::OFTREAL + feature.get_field_as_double(field_index) + else + text_value(feature.get_field_as_string(field_index)) + end + attrs[defn.get_name] = value + end + + geom = feature.get_geometry_ref + geojson = nil + if geom + geom.flatten_to_2d + geojson = JSON.parse(geom.export_to_json) + end + + attrs.merge("__geometry__" => geojson) + end + + def each_feature(path) + ds = open(path) + lyr = layer(ds) + feature_count(lyr).times.map { |i| read_feature(lyr, i) } + ensure + lyr = nil + ds = nil + end + + def text_value(input) + input.force_encoding("UTF-8") + rescue StandardError + input + end +end From 7aa36f490ce44ea5ddefbf18dd586bdce437ef8d Mon Sep 17 00:00:00 2001 From: Trey Hyde Date: Wed, 29 Jul 2026 11:36:09 -0700 Subject: [PATCH 2/5] fix: commit .tool-versions and fix CI require path Stop ignoring .tool-versions per review, and load the local gem via bundle exec -Ilib in the CI smoke step after specs pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .gitignore | 1 - .tool-versions | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 .tool-versions diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c29f269..ccb6679 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,4 +51,4 @@ jobs: run: | gdal-config --version gdal-config --libs - ruby -e "require 'gdal'; puts Gdal::Ruby::VERSION" + bundle exec ruby -Ilib -e "require 'gdal'; puts Gdal::Ruby::VERSION" diff --git a/.gitignore b/.gitignore index 463881b..1aabe01 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,3 @@ spec/examples.txt test/tmp test/version_tmp tmp -.tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..05668b7 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.3.4 From 481a40bdf2e3475e1ac6b30107bb6e84134ae168 Mon Sep 17 00:00:00 2001 From: Trey Hyde Date: Wed, 29 Jul 2026 11:54:00 -0700 Subject: [PATCH 3/5] fix: address Copilot review on build helper and importer Use Open3 for gdal-config, CSLDuplicate for metadata, ABS macro for UTM zone, and sequential OGR feature iteration in Fulcrum-shaped specs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ext/gdal-ruby/extconf_helper.rb | 15 ++++-- ext/gdal-ruby/gdal/gdal.cpp | 6 ++- ext/gdal-ruby/osr/osr.cpp | 2 +- spec/fulcrum_shapefile_import_spec.rb | 13 +++--- spec/support/fulcrum_shapefile_importer.rb | 53 +++++++++++++++++++--- 5 files changed, 71 insertions(+), 18 deletions(-) diff --git a/ext/gdal-ruby/extconf_helper.rb b/ext/gdal-ruby/extconf_helper.rb index 367baa3..6eb7263 100644 --- a/ext/gdal-ruby/extconf_helper.rb +++ b/ext/gdal-ruby/extconf_helper.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "mkmf" +require "open3" require "shellwords" require_relative "ruby-2.2-patch" @@ -13,9 +14,9 @@ def configure!(target:) gdal_config = find_executable("gdal-config") raise "gdal-config not found. Install libgdal-dev / gdal and ensure it is on PATH." unless gdal_config - version = `#{gdal_config} --version`.strip - cflags = Shellwords.split(`#{gdal_config} --cflags`.strip) - libs = Shellwords.split(`#{gdal_config} --libs`.strip) + version = gdal_config_output(gdal_config, "--version").strip + cflags = Shellwords.split(gdal_config_output(gdal_config, "--cflags").strip) + libs = Shellwords.split(gdal_config_output(gdal_config, "--libs").strip) incdirs = cflags.select { |f| f.start_with?("-I") }.map { |f| f.delete_prefix("-I") } libdirs = libs.select { |f| f.start_with?("-L") }.map { |f| f.delete_prefix("-L") } @@ -47,5 +48,13 @@ def configure!(target:) puts "Using GDAL #{version} for #{target}" create_makefile(target) end + + def gdal_config_output(gdal_config, *args) + output, status = Open3.capture2(gdal_config, *args) + raise "failed to run #{gdal_config} #{args.join(' ')}" unless status.success? + + output + end end end + diff --git a/ext/gdal-ruby/gdal/gdal.cpp b/ext/gdal-ruby/gdal/gdal.cpp index fa9bd00..531d0df 100644 --- a/ext/gdal-ruby/gdal/gdal.cpp +++ b/ext/gdal-ruby/gdal/gdal.cpp @@ -2175,10 +2175,12 @@ SWIGINTERN void GDALMajorObjectShadow_SetDescription(GDALMajorObjectShadow *self GDALSetDescription( self, pszNewDesc ); } SWIGINTERN char **GDALMajorObjectShadow_GetMetadata_Dict(GDALMajorObjectShadow *self,char const *pszDomain=""){ - return const_cast(GDALGetMetadata(self, pszDomain )); + /* GDAL 3+ returns CSLConstList. Duplicate so Ruby owns a mutable char** + without aliasing GDAL's const storage (avoids const_cast UB on mutate). */ + return CSLDuplicate(GDALGetMetadata(self, pszDomain)); } SWIGINTERN char **GDALMajorObjectShadow_GetMetadata_List(GDALMajorObjectShadow *self,char const *pszDomain=""){ - return const_cast(GDALGetMetadata(self, pszDomain )); + return CSLDuplicate(GDALGetMetadata(self, pszDomain)); } SWIGINTERN CPLErr GDALMajorObjectShadow_SetMetadata__SWIG_0(GDALMajorObjectShadow *self,char **papszMetadata,char const *pszDomain=""){ return GDALSetMetadata( self, papszMetadata, pszDomain ); diff --git a/ext/gdal-ruby/osr/osr.cpp b/ext/gdal-ruby/osr/osr.cpp index eceb3f2..c201360 100644 --- a/ext/gdal-ruby/osr/osr.cpp +++ b/ext/gdal-ruby/osr/osr.cpp @@ -2223,7 +2223,7 @@ SWIGINTERN int OSRSpatialReferenceShadow_GetUTMZone(OSRSpatialReferenceShadow *s int bNorth = FALSE; int nZone = OSRGetUTMZone( self, &bNorth ); if( !bNorth ) - nZone = -1 * ((nZone) < 0 ? -(nZone) : (nZone)); + nZone = -1 * ABS(nZone); return nZone; } SWIGINTERN OGRErr OSRSpatialReferenceShadow_SetStatePlane(OSRSpatialReferenceShadow *self,int zone,int is_nad83=1,char const *unitsname="",double units=0.0){ diff --git a/spec/fulcrum_shapefile_import_spec.rb b/spec/fulcrum_shapefile_import_spec.rb index ae51445..47eb755 100644 --- a/spec/fulcrum_shapefile_import_spec.rb +++ b/spec/fulcrum_shapefile_import_spec.rb @@ -67,7 +67,7 @@ def shp(name) end it "reads attributes with the same field accessors Fulcrum uses" do - rows = FulcrumShapefileImporter.each_feature(path) + rows = FulcrumShapefileImporter.read_features(path) expect(rows.size).to eq(2) first = rows[0] @@ -78,7 +78,7 @@ def shp(name) end it "exports geometry to GeoJSON after flatten_to_2d" do - rows = FulcrumShapefileImporter.each_feature(path) + rows = FulcrumShapefileImporter.read_features(path) geom = rows[0]["__geometry__"] expect(geom["type"]).to eq("Point") @@ -88,24 +88,25 @@ def shp(name) end it "flattens 3D points to 2 coordinates for GeoUtils consumers" do - rows = FulcrumShapefileImporter.each_feature(path) + rows = FulcrumShapefileImporter.read_features(path) three_d = rows.find { |r| r["name"] == "beta" } expect(three_d["__geometry__"]["coordinates"].size).to eq(2) end it "force-encodes string fields as UTF-8 like Fulcrum text_value" do - rows = FulcrumShapefileImporter.each_feature(path) + rows = FulcrumShapefileImporter.read_features(path) note = rows[0]["note"] expect(note.encoding).to eq(Encoding::UTF_8) expect(note).to eq("café") end - it "supports random access get_feature by cursor index" do + it "supports index-based access via set_next_by_index" do second = FulcrumShapefileImporter.read_feature(layer, 1) expect(second["name"]).to eq("beta") expect(second["count"]).to eq(10) expect(second["score"]).to eq(2.25) end + end describe "lines fixture" do @@ -158,7 +159,7 @@ def shp(name) describe "end-to-end parity with Fulcrum next_feature loop" do it "walks every feature without raising and yields geometry + attrs" do %w[points lines polygons multipoint].each do |name| - rows = FulcrumShapefileImporter.each_feature(shp(name)) + rows = FulcrumShapefileImporter.read_features(shp(name)) expect(rows).not_to be_empty rows.each do |row| expect(row).to have_key("__geometry__") diff --git a/spec/support/fulcrum_shapefile_importer.rb b/spec/support/fulcrum_shapefile_importer.rb index df793fb..bf6bb25 100644 --- a/spec/support/fulcrum_shapefile_importer.rb +++ b/spec/support/fulcrum_shapefile_importer.rb @@ -63,8 +63,16 @@ def schema_columns(layer) end end + # Prefer set_next_by_index: get_feature takes FID, which is not always 0..N-1. + def feature_at(layer, index) + layer.set_next_by_index(index) + layer.get_next_feature + end + def read_feature(layer, index) - feature = layer.get_feature(index) + feature = feature_at(layer, index) + raise "missing feature at index #{index}" if feature.nil? + info = field_info(layer) attrs = {} @@ -94,13 +102,45 @@ def read_feature(layer, index) attrs.merge("__geometry__" => geojson) end - def each_feature(path) + # Sequential scan matching Fulcrum's next_feature loop. + def read_features(path) ds = open(path) lyr = layer(ds) - feature_count(lyr).times.map { |i| read_feature(lyr, i) } - ensure - lyr = nil - ds = nil + lyr.reset_reading + + rows = [] + while (feature = lyr.get_next_feature) + info = field_info(lyr) + attrs = {} + + field_count(lyr).times do |field_index| + defn = info.get_field_defn(field_index) + value = + case defn.get_type + when Gdal::Ogr::OFTSTRING + text_value(feature.get_field_as_string(field_index)) + when Gdal::Ogr::OFTINTEGER + feature.get_field_as_integer(field_index) + when Gdal::Ogr::OFTREAL + feature.get_field_as_double(field_index) + else + text_value(feature.get_field_as_string(field_index)) + end + attrs[defn.get_name] = value + end + + geom = feature.get_geometry_ref + if geom + geom.flatten_to_2d + attrs["__geometry__"] = JSON.parse(geom.export_to_json) + else + attrs["__geometry__"] = nil + end + + rows << attrs + end + + rows end def text_value(input) @@ -109,3 +149,4 @@ def text_value(input) input end end + From 926417e1e574f326d77d6e05738fab2091cdec18 Mon Sep 17 00:00:00 2001 From: Trey Hyde Date: Wed, 29 Jul 2026 12:32:40 -0700 Subject: [PATCH 4/5] fix: free duplicated metadata CSL and polish review notes Destroy CSLDuplicate results after SWIG converts GetMetadata to Ruby. Also use https github_repo metadata, require "gdal" in README, namespace spec fixtures, and dedupe Fulcrum importer field extraction. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 2 +- ext/gdal-ruby/gdal/gdal.cpp | 4 ++ gdal.gemspec | 2 +- spec/fulcrum_shapefile_import_spec.rb | 6 +- spec/ruby3_warnings_spec.rb | 2 +- spec/spec_helper.rb | 4 +- spec/support/fulcrum_shapefile_importer.rb | 81 ++++++++-------------- 7 files changed, 43 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 8273864..3dec173 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ bundle exec rake spec ## Usage ```ruby -require "gdal-ruby/ogr" +require "gdal" puts Gdal::Ogr .create_geometry_from_wkt("POINT (30 10)") diff --git a/ext/gdal-ruby/gdal/gdal.cpp b/ext/gdal-ruby/gdal/gdal.cpp index 531d0df..d78f46b 100644 --- a/ext/gdal-ruby/gdal/gdal.cpp +++ b/ext/gdal-ruby/gdal/gdal.cpp @@ -4949,6 +4949,8 @@ _gdal_wrap_MajorObject_get_metadata_dict(int argc, VALUE *argv, VALUE self) { stringarray++; } } + /* GetMetadata_Dict duplicates via CSLDuplicate; free after Ruby conversion. */ + CSLDestroy(result); } if (alloc2 == SWIG_NEWOBJ) delete[] buf2; return vresult; @@ -5020,6 +5022,8 @@ _gdal_wrap_MajorObject_get_metadata_list(int argc, VALUE *argv, VALUE self) { rb_ary_push(vresult, nm); } } + /* GetMetadata_List duplicates via CSLDuplicate; free after Ruby conversion. */ + CSLDestroy(result); } if (alloc2 == SWIG_NEWOBJ) delete[] buf2; return vresult; diff --git a/gdal.gemspec b/gdal.gemspec index 9b58262..379fa85 100644 --- a/gdal.gemspec +++ b/gdal.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |gem| "changelog_uri" => "https://github.com/fulcrumapp/gdal-ruby/blob/main/CHANGELOG.md", "homepage_uri" => gem.homepage, "source_code_uri" => "https://github.com/fulcrumapp/gdal-ruby", - "github_repo" => "ssh://github.com/fulcrumapp/gdal-ruby", + "github_repo" => "https://github.com/fulcrumapp/gdal-ruby", "allowed_push_host" => "https://rubygems.pkg.github.com/fulcrumapp" } diff --git a/spec/fulcrum_shapefile_import_spec.rb b/spec/fulcrum_shapefile_import_spec.rb index 47eb755..755ca20 100644 --- a/spec/fulcrum_shapefile_import_spec.rb +++ b/spec/fulcrum_shapefile_import_spec.rb @@ -5,7 +5,7 @@ RSpec.describe "Fulcrum Import::Formats::Shapefile surface" do def shp(name) - FIXTURES.join(name, "#{File.basename(name)}.shp") + SpecSupport::FIXTURES.join(name, "#{File.basename(name)}.shp") end describe "constants used by Fulcrum schema mapping" do @@ -30,13 +30,13 @@ def shp(name) describe "open + layer access" do it "opens a .shp path like Fulcrum file_path usage" do - ds = FulcrumShapefileImporter.open(FIXTURES.join("flat/points.shp")) + ds = FulcrumShapefileImporter.open(SpecSupport::FIXTURES.join("flat/points.shp")) expect(ds).not_to be_nil expect(FulcrumShapefileImporter.layer(ds).get_feature_count).to eq(2) end it "opens a directory datasource containing a shapefile" do - ds = FulcrumShapefileImporter.open(FIXTURES.join("points")) + ds = FulcrumShapefileImporter.open(SpecSupport::FIXTURES.join("points")) expect(FulcrumShapefileImporter.feature_count(FulcrumShapefileImporter.layer(ds))).to eq(2) end end diff --git a/spec/ruby3_warnings_spec.rb b/spec/ruby3_warnings_spec.rb index 6defd04..7c4ed7f 100644 --- a/spec/ruby3_warnings_spec.rb +++ b/spec/ruby3_warnings_spec.rb @@ -10,7 +10,7 @@ require "gdal" g = Gdal::Ogr.create_geometry_from_wkt("POINT (1 2)") puts g.export_to_json - ds = Gdal::Ogr.open(#{FIXTURES.join('flat/points.shp').to_s.inspect}) + ds = Gdal::Ogr.open(#{SpecSupport::FIXTURES.join('flat/points.shp').to_s.inspect}) layer = ds.get_layer(0) f = layer.get_feature(0) puts f.get_field_as_string(0) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4271bfb..d69376b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,7 +4,9 @@ require "pathname" require "gdal" -FIXTURES = Pathname.new(__dir__).join("fixtures/shapefiles").expand_path +module SpecSupport + FIXTURES = Pathname.new(__dir__).join("fixtures/shapefiles").expand_path +end RSpec.configure do |config| config.expect_with :rspec do |expectations| diff --git a/spec/support/fulcrum_shapefile_importer.rb b/spec/support/fulcrum_shapefile_importer.rb index bf6bb25..538570e 100644 --- a/spec/support/fulcrum_shapefile_importer.rb +++ b/spec/support/fulcrum_shapefile_importer.rb @@ -50,7 +50,7 @@ def geometry_kind(layer) def schema_columns(layer) info = field_info(layer) - field_count(layer).times.map do |index| + info.get_field_count.times.map do |index| defn = info.get_field_defn(index) type = case defn.get_type @@ -74,9 +74,30 @@ def read_feature(layer, index) raise "missing feature at index #{index}" if feature.nil? info = field_info(layer) - attrs = {} + attrs = read_attrs(feature, info) + attrs.merge("__geometry__" => geometry_json(feature)) + end + + # Sequential scan matching Fulcrum's next_feature loop. + def read_features(path) + ds = open(path) + lyr = layer(ds) + lyr.reset_reading + + info = field_info(lyr) + rows = [] + while (feature = lyr.get_next_feature) + attrs = read_attrs(feature, info) + attrs["__geometry__"] = geometry_json(feature) + rows << attrs + end - field_count(layer).times do |field_index| + rows + end + + def read_attrs(feature, info) + attrs = {} + info.get_field_count.times do |field_index| defn = info.get_field_defn(field_index) value = case defn.get_type @@ -91,56 +112,15 @@ def read_feature(layer, index) end attrs[defn.get_name] = value end - - geom = feature.get_geometry_ref - geojson = nil - if geom - geom.flatten_to_2d - geojson = JSON.parse(geom.export_to_json) - end - - attrs.merge("__geometry__" => geojson) + attrs end - # Sequential scan matching Fulcrum's next_feature loop. - def read_features(path) - ds = open(path) - lyr = layer(ds) - lyr.reset_reading - - rows = [] - while (feature = lyr.get_next_feature) - info = field_info(lyr) - attrs = {} - - field_count(lyr).times do |field_index| - defn = info.get_field_defn(field_index) - value = - case defn.get_type - when Gdal::Ogr::OFTSTRING - text_value(feature.get_field_as_string(field_index)) - when Gdal::Ogr::OFTINTEGER - feature.get_field_as_integer(field_index) - when Gdal::Ogr::OFTREAL - feature.get_field_as_double(field_index) - else - text_value(feature.get_field_as_string(field_index)) - end - attrs[defn.get_name] = value - end - - geom = feature.get_geometry_ref - if geom - geom.flatten_to_2d - attrs["__geometry__"] = JSON.parse(geom.export_to_json) - else - attrs["__geometry__"] = nil - end - - rows << attrs - end + def geometry_json(feature) + geom = feature.get_geometry_ref + return nil unless geom - rows + geom.flatten_to_2d + JSON.parse(geom.export_to_json) end def text_value(input) @@ -149,4 +129,3 @@ def text_value(input) input end end - From dcfec9e9d6c763dcc9599b112b72d609e9603042 Mon Sep 17 00:00:00 2001 From: Trey Hyde Date: Wed, 29 Jul 2026 13:41:56 -0700 Subject: [PATCH 5/5] fix: harden gem packaging and release publish path Make gem.files resilient without git, fix GITHUB_TOKEN expression in release.yml, run specs before gem push, and use index-based feature access in the Ruby 3 warning regression. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/release.yml | 5 ++++- gdal.gemspec | 22 +++++++++++++++++++--- spec/ruby3_warnings_spec.rb | 3 ++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3df602..65f66ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,6 +47,9 @@ jobs: ruby-version: '3.3' bundler-cache: true + - name: Verify compile and specs + run: bundle exec rake spec + - name: Build gem run: | gem build gdal.gemspec @@ -54,7 +57,7 @@ jobs: - name: Publish to GitHub Packages env: - GEM_HOST_API_KEY: Bearer ${{ secrets.GITHUB_TOKEN }} + GEM_HOST_API_KEY: ${{ secrets.GITHUB_TOKEN }} run: | mkdir -p ~/.gem printf '%s\n' '---' ":github: ${GEM_HOST_API_KEY}" > ~/.gem/credentials diff --git a/gdal.gemspec b/gdal.gemspec index 379fa85..61fec69 100644 --- a/gdal.gemspec +++ b/gdal.gemspec @@ -28,10 +28,26 @@ Gem::Specification.new do |gem| } gem.files = Dir.chdir(__dir__) do - `git ls-files -z`.split("\x0").reject do |f| - f.start_with?("spec/", "test/", ".github/", ".travis.yml") - end + tracked = + if system("git", "rev-parse", "--is-inside-work-tree", out: File::NULL, err: File::NULL) + `git ls-files -z`.split("\x0") + else + Dir.glob("**/*", File::FNM_DOTMATCH) + end + + tracked + .reject(&:empty?) + .reject { |f| f == "." || f == ".." || f.end_with?("/.") || f.end_with?("/..") } + .reject { |f| File.directory?(f) } + .reject do |f| + f.start_with?("spec/", "test/", ".github/", "tmp/", "pkg/") || + f == ".travis.yml" || + f.end_with?(".bundle", ".so", ".o", ".gem") + end end + + raise "gdal.gemspec: gem.files is empty; cannot package a valid gem" if gem.files.empty? + gem.require_paths = ["lib"] gem.extensions = [ "ext/gdal-ruby/gdal/extconf.rb", diff --git a/spec/ruby3_warnings_spec.rb b/spec/ruby3_warnings_spec.rb index 7c4ed7f..992b432 100644 --- a/spec/ruby3_warnings_spec.rb +++ b/spec/ruby3_warnings_spec.rb @@ -12,7 +12,8 @@ puts g.export_to_json ds = Gdal::Ogr.open(#{SpecSupport::FIXTURES.join('flat/points.shp').to_s.inspect}) layer = ds.get_layer(0) - f = layer.get_feature(0) + layer.set_next_by_index(0) + f = layer.get_next_feature puts f.get_field_as_string(0) RUBY