I eventually achieved this requirement after some more research.  
As some here pointed out, the solution given in most places was this (Converted to kotlin):
class SquareButton(context: Context, attrs: AttributeSet) : Button(context, attrs) {
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val width: Int = MeasureSpec.getSize(widthMeasureSpec);
        setMeasuredDimension(width, width)
    }
}
The problem with MeasureSpec.getSize in the scenario is that (along with MeasureSpec.getMode) it contains the sizing "rule" for the width.
In this case: size:(Available size) + mode:(AT_MOST) = the maximum width allowed for the button  
The other option is to get the minimum width, already computed by the Button class:
class SquareButton(context: Context, attrs: AttributeSet) : Button(context, attrs) {
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val width: Int = measuredWidth
        setMeasuredDimension(width, width)
    }
}
getMeasuredWidth() returns the width measurement set in onMeasure() via setMeasuredDimension().  
What is the difference between MeasureSpec.getSize(widthMeasureSpec) and getWidth()?
The measuredWidth property is set to the wrap_content value when calling super.onMeasure. This is the wanted value for the width and height.