Instead of instantiating a specific library cell in your RTL, it would be more reusable and readable to instead use a generic cell that represents a family of closely related libcells that differ only in their physical characteristics.
In RTL the generic cell would be declared as a synthesizable always-block. Initially, depending on how exotic the libcells are, the tool might not even be able to map an instance to any of those you had in mind. But that’s OK, let it choose a suboptimal implementation. (For estimation, that will be close enough anyway.)
You can control the actual choice of libcells using change_link before mapping. For example, if you instantiated
module DFF(input CLK, input D, output logic Q, QN); always_ff @(posedge CLK) begin Q <= D; end assign QN = ~Q; endmodule
set DFFs [get_cells * -filter "@ref_name == DFF"] change_link $DFFs my_lib/my_libcell
When you need more nuance in the RTL, add attributes to the instantiations, and then change_link depending on them in addition to the name of the generic reference cell.