June 14, 2019·4 min read·engineeringrubyrubygemspackaging

RubyGems 3.0.4 handles installation paths more consistently

RubyGems 3.0.4 repairs uninstalling from a symlinked GEM_HOME and restores gem build behavior. The maintenance work concerns how package operations interpret their environment.

Two filesystem paths can identify the same directory while comparing as different strings. A package manager that uses those strings to decide which installation it owns can therefore reject a legitimate operation. RubyGems 3.0.4, released June 14, includes support for uninstalling from a symlinked GEM_HOME. It is a small release change with a concrete explanation in the implementation.

The 3.0.4 uninstaller initializes its gem-home path with File.realpath. That resolves the selected installation directory before the uninstaller compares it with the base directory of installed specifications. The operation can then recognize the intended installation even when the user reached it through a symlink.

I find Ruby approachable, but the environment around a Ruby application can still contain several layers of indirection. Package tools need to interpret those layers consistently. A reliable uninstall operation is part of managing that environment, even when application source remains unchanged.

Repository ownership depends on path identity

The uninstaller gathers specifications matching the requested gem and partitions them by installation location. A matching name alone is insufficient. The same gem can exist in another repository, and a command directed at one installation should not remove an unrelated copy merely because the names coincide.

Resolving the gem-home path supports that distinction. The comparison concerns the actual selected directory, rather than only the spelling supplied by an environment variable or command option. This avoids confusing an alternate route to one installation with a genuinely separate installation.

The uninstaller also retains further checks before removal. It verifies that the specification's path belongs to the expected gem location and checks that the base directory is writable. Supporting a symlinked gem home does not mean removing the requirement to identify the destination. The implementation first establishes a usable identity, then applies its removal rules to that identity.

This is a useful distinction from accepting every path alias without inspection. A path can be operationally convenient for a user while still requiring normalization before a tool compares locations. The convenience and the ownership check serve different purposes, and the implementation has to accommodate both.

A gem installation includes more than the directory containing library source. The uninstaller handles cached packages, specification files, extensions, and documentation. It also considers installed executables. Removing one version must account for executables still supplied by another installed version of the same gem.

That makes the command a coordinated operation over several related artifacts. An application that no longer requires a gem does not itself describe which installed files are safe to remove. The package manager uses its specifications and dependency information to make those relationships explicit.

The implementation checks dependent gems unless the configured operation overrides that check. A removal request can therefore fail because another installed package still needs the selected version. This is a different failure from selecting the wrong installation directory. Correctly recognizing the directory allows the command to reach the dependency decision appropriate to the requested gem.

I regard these distinctions as useful maintenance behavior. An error should explain the actual obstacle to an operation. Reporting that a gem is outside the selected home when the difference is only a symlink sends the user toward the wrong repair.

Building a package also needs a defined directory context

The release notes also identify restored gem build behavior and a new -C option. The build command in the release sources loads a gemspec and passes it to Gem::Package.build. It supports explicit output naming as well as specification validation options.

Directory context matters because a gemspec can refer to files using relative paths. A caller supplying a path to a specification and a caller first changing into its directory can expose different current working directories to the evaluated code. That difference can affect which files the specification describes.

The release's build-command work therefore concerns an existing workflow contract, not a new package format. A package author needs to know which directory gives relative names their meaning. The command implementation and its tests must preserve that meaning for the supported invocation forms.

Test isolation is part of package-manager reliability

Several other release entries concern the project's own tests. The notes include cleanup of utility gems leaked by indexer tests and cleanup of default specification directories. Those are test-environment changes, rather than claims that every application has a matching runtime defect.

A test that leaves installed metadata behind can change what the next test observes. Package tools are particularly sensitive to such state because discovery is one of their jobs. A later test may find a specification it did not create and take a different branch as a result. Cleaning up that state keeps the observed behavior attributable to the case being exercised.

RubyGems 3.0.4 consequently improves both package operations and the environment used to verify them. The symlinked-home change supplies a concrete example of the same requirement in ordinary use. A path spelling must resolve to the intended installation before its specifications, dependencies, and installed files can be managed consistently.