github.com/MrKrisYu/mobile@v0.0.0-20230923092425-9be92a9aeacc/cmd/gomobile/build_apple.go (about)

     1  // Copyright 2015 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/x509"
    10  	"encoding/pem"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"path"
    16  	"path/filepath"
    17  	"strings"
    18  	"text/template"
    19  
    20  	"golang.org/x/tools/go/packages"
    21  )
    22  
    23  func goAppleBuild(pkg *packages.Package, bundleID string, targets []targetInfo) (map[string]bool, error) {
    24  	src := pkg.PkgPath
    25  	if buildO != "" && !strings.HasSuffix(buildO, ".app") {
    26  		return nil, fmt.Errorf("-o must have an .app for -target=ios")
    27  	}
    28  
    29  	productName := rfc1034Label(path.Base(pkg.PkgPath))
    30  	if productName == "" {
    31  		productName = "ProductName" // like xcode.
    32  	}
    33  
    34  	infoplist := new(bytes.Buffer)
    35  	if err := infoplistTmpl.Execute(infoplist, infoplistTmplData{
    36  		BundleID: bundleID + "." + productName,
    37  		Name:     strings.Title(path.Base(pkg.PkgPath)),
    38  	}); err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	// Detect the team ID
    43  	teamID, err := detectTeamID()
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	projPbxproj := new(bytes.Buffer)
    49  	if err := projPbxprojTmpl.Execute(projPbxproj, projPbxprojTmplData{
    50  		TeamID: teamID,
    51  	}); err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	files := []struct {
    56  		name     string
    57  		contents []byte
    58  	}{
    59  		{tmpdir + "/main.xcodeproj/project.pbxproj", projPbxproj.Bytes()},
    60  		{tmpdir + "/main/Info.plist", infoplist.Bytes()},
    61  		{tmpdir + "/main/Images.xcassets/AppIcon.appiconset/Contents.json", []byte(contentsJSON)},
    62  	}
    63  
    64  	for _, file := range files {
    65  		if err := mkdir(filepath.Dir(file.name)); err != nil {
    66  			return nil, err
    67  		}
    68  		if buildX {
    69  			printcmd("echo \"%s\" > %s", file.contents, file.name)
    70  		}
    71  		if !buildN {
    72  			if err := ioutil.WriteFile(file.name, file.contents, 0644); err != nil {
    73  				return nil, err
    74  			}
    75  		}
    76  	}
    77  
    78  	// We are using lipo tool to build multiarchitecture binaries.
    79  	cmd := exec.Command(
    80  		"xcrun", "lipo",
    81  		"-o", filepath.Join(tmpdir, "main/main"),
    82  		"-create",
    83  	)
    84  
    85  	var nmpkgs map[string]bool
    86  	builtArch := map[string]bool{}
    87  	for _, t := range targets {
    88  		// Only one binary per arch allowed
    89  		// e.g. ios/arm64 + iossimulator/amd64
    90  		if builtArch[t.arch] {
    91  			continue
    92  		}
    93  		builtArch[t.arch] = true
    94  
    95  		path := filepath.Join(tmpdir, t.platform, t.arch)
    96  
    97  		// Disable DWARF; see golang.org/issues/25148.
    98  		if err := goBuild(src, appleEnv[t.String()], "-ldflags=-w", "-o="+path); err != nil {
    99  			return nil, err
   100  		}
   101  		if nmpkgs == nil {
   102  			var err error
   103  			nmpkgs, err = extractPkgs(appleNM, path)
   104  			if err != nil {
   105  				return nil, err
   106  			}
   107  		}
   108  		cmd.Args = append(cmd.Args, path)
   109  
   110  	}
   111  
   112  	if err := runCmd(cmd); err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	// TODO(jbd): Set the launcher icon.
   117  	if err := appleCopyAssets(pkg, tmpdir); err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	// Build and move the release build to the output directory.
   122  	cmdStrings := []string{
   123  		"xcodebuild",
   124  		"-configuration", "Release",
   125  		"-project", tmpdir + "/main.xcodeproj",
   126  		"-allowProvisioningUpdates",
   127  		"DEVELOPMENT_TEAM=" + teamID,
   128  	}
   129  
   130  	cmd = exec.Command("xcrun", cmdStrings...)
   131  	if err := runCmd(cmd); err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	// TODO(jbd): Fallback to copying if renaming fails.
   136  	if buildO == "" {
   137  		n := pkg.PkgPath
   138  		if n == "." {
   139  			// use cwd name
   140  			cwd, err := os.Getwd()
   141  			if err != nil {
   142  				return nil, fmt.Errorf("cannot create .app; cannot get the current working dir: %v", err)
   143  			}
   144  			n = cwd
   145  		}
   146  		n = path.Base(n)
   147  		buildO = n + ".app"
   148  	}
   149  	if buildX {
   150  		printcmd("mv %s %s", tmpdir+"/build/Release-iphoneos/main.app", buildO)
   151  	}
   152  	if !buildN {
   153  		// if output already exists, remove.
   154  		if err := os.RemoveAll(buildO); err != nil {
   155  			return nil, err
   156  		}
   157  		if err := os.Rename(tmpdir+"/build/Release-iphoneos/main.app", buildO); err != nil {
   158  			return nil, err
   159  		}
   160  	}
   161  	return nmpkgs, nil
   162  }
   163  
   164  func detectTeamID() (string, error) {
   165  	// Grabs the first certificate for "Apple Development"; will not work if there
   166  	// are multiple certificates and the first is not desired.
   167  	cmd := exec.Command(
   168  		"security", "find-certificate",
   169  		"-c", "Apple Development", "-p",
   170  	)
   171  	pemString, err := cmd.Output()
   172  	if err != nil {
   173  		err = fmt.Errorf("failed to pull the signing certificate to determine your team ID: %v", err)
   174  		return "", err
   175  	}
   176  
   177  	block, _ := pem.Decode(pemString)
   178  	if block == nil {
   179  		err = fmt.Errorf("failed to decode the PEM to determine your team ID: %s", pemString)
   180  		return "", err
   181  	}
   182  
   183  	cert, err := x509.ParseCertificate(block.Bytes)
   184  	if err != nil {
   185  		err = fmt.Errorf("failed to parse your signing certificate to determine your team ID: %v", err)
   186  		return "", err
   187  	}
   188  
   189  	if len(cert.Subject.OrganizationalUnit) == 0 {
   190  		err = fmt.Errorf("the signing certificate has no organizational unit (team ID)")
   191  		return "", err
   192  	}
   193  
   194  	return cert.Subject.OrganizationalUnit[0], nil
   195  }
   196  
   197  func appleCopyAssets(pkg *packages.Package, xcodeProjDir string) error {
   198  	dstAssets := xcodeProjDir + "/main/assets"
   199  	if err := mkdir(dstAssets); err != nil {
   200  		return err
   201  	}
   202  
   203  	// TODO(hajimehoshi): This works only with Go tools that assume all source files are in one directory.
   204  	// Fix this to work with other Go tools.
   205  	srcAssets := filepath.Join(filepath.Dir(pkg.GoFiles[0]), "assets")
   206  	fi, err := os.Stat(srcAssets)
   207  	if err != nil {
   208  		if os.IsNotExist(err) {
   209  			// skip walking through the directory to deep copy.
   210  			return nil
   211  		}
   212  		return err
   213  	}
   214  	if !fi.IsDir() {
   215  		// skip walking through to deep copy.
   216  		return nil
   217  	}
   218  	// if assets is a symlink, follow the symlink.
   219  	srcAssets, err = filepath.EvalSymlinks(srcAssets)
   220  	if err != nil {
   221  		return err
   222  	}
   223  	return filepath.Walk(srcAssets, func(path string, info os.FileInfo, err error) error {
   224  		if err != nil {
   225  			return err
   226  		}
   227  		if name := filepath.Base(path); strings.HasPrefix(name, ".") {
   228  			// Do not include the hidden files.
   229  			return nil
   230  		}
   231  		if info.IsDir() {
   232  			return nil
   233  		}
   234  		dst := dstAssets + "/" + path[len(srcAssets)+1:]
   235  		return copyFile(dst, path)
   236  	})
   237  }
   238  
   239  type infoplistTmplData struct {
   240  	BundleID string
   241  	Name     string
   242  }
   243  
   244  var infoplistTmpl = template.Must(template.New("infoplist").Parse(`<?xml version="1.0" encoding="UTF-8"?>
   245  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   246  <plist version="1.0">
   247  <dict>
   248    <key>CFBundleDevelopmentRegion</key>
   249    <string>en</string>
   250    <key>CFBundleExecutable</key>
   251    <string>main</string>
   252    <key>CFBundleIdentifier</key>
   253    <string>{{.BundleID}}</string>
   254    <key>CFBundleInfoDictionaryVersion</key>
   255    <string>6.0</string>
   256    <key>CFBundleName</key>
   257    <string>{{.Name}}</string>
   258    <key>CFBundlePackageType</key>
   259    <string>APPL</string>
   260    <key>CFBundleShortVersionString</key>
   261    <string>1.0</string>
   262    <key>CFBundleSignature</key>
   263    <string>????</string>
   264    <key>CFBundleVersion</key>
   265    <string>1</string>
   266    <key>LSRequiresIPhoneOS</key>
   267    <true/>
   268    <key>UILaunchStoryboardName</key>
   269    <string>LaunchScreen</string>
   270    <key>UIRequiredDeviceCapabilities</key>
   271    <array>
   272      <string>armv7</string>
   273    </array>
   274    <key>UISupportedInterfaceOrientations</key>
   275    <array>
   276      <string>UIInterfaceOrientationPortrait</string>
   277      <string>UIInterfaceOrientationLandscapeLeft</string>
   278      <string>UIInterfaceOrientationLandscapeRight</string>
   279    </array>
   280    <key>UISupportedInterfaceOrientations~ipad</key>
   281    <array>
   282      <string>UIInterfaceOrientationPortrait</string>
   283      <string>UIInterfaceOrientationPortraitUpsideDown</string>
   284      <string>UIInterfaceOrientationLandscapeLeft</string>
   285      <string>UIInterfaceOrientationLandscapeRight</string>
   286    </array>
   287  </dict>
   288  </plist>
   289  `))
   290  
   291  type projPbxprojTmplData struct {
   292  	TeamID string
   293  }
   294  
   295  var projPbxprojTmpl = template.Must(template.New("projPbxproj").Parse(`// !$*UTF8*$!
   296  {
   297    archiveVersion = 1;
   298    classes = {
   299    };
   300    objectVersion = 46;
   301    objects = {
   302  
   303  /* Begin PBXBuildFile section */
   304      254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 254BB84E1B1FD08900C56DE9 /* Images.xcassets */; };
   305      254BB8681B1FD16500C56DE9 /* main in Resources */ = {isa = PBXBuildFile; fileRef = 254BB8671B1FD16500C56DE9 /* main */; };
   306      25FB30331B30FDEE0005924C /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 25FB30321B30FDEE0005924C /* assets */; };
   307  /* End PBXBuildFile section */
   308  
   309  /* Begin PBXFileReference section */
   310      254BB83E1B1FD08900C56DE9 /* main.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = main.app; sourceTree = BUILT_PRODUCTS_DIR; };
   311      254BB8421B1FD08900C56DE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
   312      254BB84E1B1FD08900C56DE9 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
   313      254BB8671B1FD16500C56DE9 /* main */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = main; sourceTree = "<group>"; };
   314      25FB30321B30FDEE0005924C /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = main/assets; sourceTree = "<group>"; };
   315  /* End PBXFileReference section */
   316  
   317  /* Begin PBXGroup section */
   318      254BB8351B1FD08900C56DE9 = {
   319        isa = PBXGroup;
   320        children = (
   321          25FB30321B30FDEE0005924C /* assets */,
   322          254BB8401B1FD08900C56DE9 /* main */,
   323          254BB83F1B1FD08900C56DE9 /* Products */,
   324        );
   325        sourceTree = "<group>";
   326        usesTabs = 0;
   327      };
   328      254BB83F1B1FD08900C56DE9 /* Products */ = {
   329        isa = PBXGroup;
   330        children = (
   331          254BB83E1B1FD08900C56DE9 /* main.app */,
   332        );
   333        name = Products;
   334        sourceTree = "<group>";
   335      };
   336      254BB8401B1FD08900C56DE9 /* main */ = {
   337        isa = PBXGroup;
   338        children = (
   339          254BB8671B1FD16500C56DE9 /* main */,
   340          254BB84E1B1FD08900C56DE9 /* Images.xcassets */,
   341          254BB8411B1FD08900C56DE9 /* Supporting Files */,
   342        );
   343        path = main;
   344        sourceTree = "<group>";
   345      };
   346      254BB8411B1FD08900C56DE9 /* Supporting Files */ = {
   347        isa = PBXGroup;
   348        children = (
   349          254BB8421B1FD08900C56DE9 /* Info.plist */,
   350        );
   351        name = "Supporting Files";
   352        sourceTree = "<group>";
   353      };
   354  /* End PBXGroup section */
   355  
   356  /* Begin PBXNativeTarget section */
   357      254BB83D1B1FD08900C56DE9 /* main */ = {
   358        isa = PBXNativeTarget;
   359        buildConfigurationList = 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */;
   360        buildPhases = (
   361          254BB83C1B1FD08900C56DE9 /* Resources */,
   362        );
   363        buildRules = (
   364        );
   365        dependencies = (
   366        );
   367        name = main;
   368        productName = main;
   369        productReference = 254BB83E1B1FD08900C56DE9 /* main.app */;
   370        productType = "com.apple.product-type.application";
   371      };
   372  /* End PBXNativeTarget section */
   373  
   374  /* Begin PBXProject section */
   375      254BB8361B1FD08900C56DE9 /* Project object */ = {
   376        isa = PBXProject;
   377        attributes = {
   378          LastUpgradeCheck = 0630;
   379          ORGANIZATIONNAME = Developer;
   380          TargetAttributes = {
   381            254BB83D1B1FD08900C56DE9 = {
   382              CreatedOnToolsVersion = 6.3.1;
   383              DevelopmentTeam = {{.TeamID}};
   384            };
   385          };
   386        };
   387        buildConfigurationList = 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */;
   388        compatibilityVersion = "Xcode 3.2";
   389        developmentRegion = English;
   390        hasScannedForEncodings = 0;
   391        knownRegions = (
   392          en,
   393          Base,
   394        );
   395        mainGroup = 254BB8351B1FD08900C56DE9;
   396        productRefGroup = 254BB83F1B1FD08900C56DE9 /* Products */;
   397        projectDirPath = "";
   398        projectRoot = "";
   399        targets = (
   400          254BB83D1B1FD08900C56DE9 /* main */,
   401        );
   402      };
   403  /* End PBXProject section */
   404  
   405  /* Begin PBXResourcesBuildPhase section */
   406      254BB83C1B1FD08900C56DE9 /* Resources */ = {
   407        isa = PBXResourcesBuildPhase;
   408        buildActionMask = 2147483647;
   409        files = (
   410          25FB30331B30FDEE0005924C /* assets in Resources */,
   411          254BB8681B1FD16500C56DE9 /* main in Resources */,
   412          254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */,
   413        );
   414        runOnlyForDeploymentPostprocessing = 0;
   415      };
   416  /* End PBXResourcesBuildPhase section */
   417  
   418  /* Begin XCBuildConfiguration section */
   419      254BB8601B1FD08900C56DE9 /* Release */ = {
   420        isa = XCBuildConfiguration;
   421        buildSettings = {
   422          ALWAYS_SEARCH_USER_PATHS = NO;
   423          CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
   424          CLANG_CXX_LIBRARY = "libc++";
   425          CLANG_ENABLE_MODULES = YES;
   426          CLANG_ENABLE_OBJC_ARC = YES;
   427          CLANG_WARN_BOOL_CONVERSION = YES;
   428          CLANG_WARN_CONSTANT_CONVERSION = YES;
   429          CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
   430          CLANG_WARN_EMPTY_BODY = YES;
   431          CLANG_WARN_ENUM_CONVERSION = YES;
   432          CLANG_WARN_INT_CONVERSION = YES;
   433          CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
   434          CLANG_WARN_UNREACHABLE_CODE = YES;
   435          CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
   436          "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
   437          COPY_PHASE_STRIP = NO;
   438          DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
   439          ENABLE_NS_ASSERTIONS = NO;
   440          ENABLE_STRICT_OBJC_MSGSEND = YES;
   441          GCC_C_LANGUAGE_STANDARD = gnu99;
   442          GCC_NO_COMMON_BLOCKS = YES;
   443          GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
   444          GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
   445          GCC_WARN_UNDECLARED_SELECTOR = YES;
   446          GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
   447          GCC_WARN_UNUSED_FUNCTION = YES;
   448          GCC_WARN_UNUSED_VARIABLE = YES;
   449          MTL_ENABLE_DEBUG_INFO = NO;
   450          SDKROOT = iphoneos;
   451          TARGETED_DEVICE_FAMILY = "1,2";
   452          VALIDATE_PRODUCT = YES;
   453          ENABLE_BITCODE = YES;
   454        };
   455        name = Release;
   456      };
   457      254BB8631B1FD08900C56DE9 /* Release */ = {
   458        isa = XCBuildConfiguration;
   459        buildSettings = {
   460          ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
   461          INFOPLIST_FILE = main/Info.plist;
   462          LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
   463          PRODUCT_NAME = "$(TARGET_NAME)";
   464        };
   465        name = Release;
   466      };
   467  /* End XCBuildConfiguration section */
   468  
   469  /* Begin XCConfigurationList section */
   470      254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */ = {
   471        isa = XCConfigurationList;
   472        buildConfigurations = (
   473          254BB8601B1FD08900C56DE9 /* Release */,
   474        );
   475        defaultConfigurationIsVisible = 0;
   476        defaultConfigurationName = Release;
   477      };
   478      254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */ = {
   479        isa = XCConfigurationList;
   480        buildConfigurations = (
   481          254BB8631B1FD08900C56DE9 /* Release */,
   482        );
   483        defaultConfigurationIsVisible = 0;
   484        defaultConfigurationName = Release;
   485      };
   486  /* End XCConfigurationList section */
   487    };
   488    rootObject = 254BB8361B1FD08900C56DE9 /* Project object */;
   489  }
   490  `))
   491  
   492  const contentsJSON = `{
   493    "images" : [
   494      {
   495        "idiom" : "iphone",
   496        "size" : "29x29",
   497        "scale" : "2x"
   498      },
   499      {
   500        "idiom" : "iphone",
   501        "size" : "29x29",
   502        "scale" : "3x"
   503      },
   504      {
   505        "idiom" : "iphone",
   506        "size" : "40x40",
   507        "scale" : "2x"
   508      },
   509      {
   510        "idiom" : "iphone",
   511        "size" : "40x40",
   512        "scale" : "3x"
   513      },
   514      {
   515        "idiom" : "iphone",
   516        "size" : "60x60",
   517        "scale" : "2x"
   518      },
   519      {
   520        "idiom" : "iphone",
   521        "size" : "60x60",
   522        "scale" : "3x"
   523      },
   524      {
   525        "idiom" : "ipad",
   526        "size" : "29x29",
   527        "scale" : "1x"
   528      },
   529      {
   530        "idiom" : "ipad",
   531        "size" : "29x29",
   532        "scale" : "2x"
   533      },
   534      {
   535        "idiom" : "ipad",
   536        "size" : "40x40",
   537        "scale" : "1x"
   538      },
   539      {
   540        "idiom" : "ipad",
   541        "size" : "40x40",
   542        "scale" : "2x"
   543      },
   544      {
   545        "idiom" : "ipad",
   546        "size" : "76x76",
   547        "scale" : "1x"
   548      },
   549      {
   550        "idiom" : "ipad",
   551        "size" : "76x76",
   552        "scale" : "2x"
   553      }
   554    ],
   555    "info" : {
   556      "version" : 1,
   557      "author" : "xcode"
   558    }
   559  }
   560  `
   561  
   562  // rfc1034Label sanitizes the name to be usable in a uniform type identifier.
   563  // The sanitization is similar to xcode's rfc1034identifier macro that
   564  // replaces illegal characters (not conforming the rfc1034 label rule) with '-'.
   565  func rfc1034Label(name string) string {
   566  	// * Uniform type identifier:
   567  	//
   568  	// According to
   569  	// https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html
   570  	//
   571  	// A uniform type identifier is a Unicode string that usually contains characters
   572  	// in the ASCII character set. However, only a subset of the ASCII characters are
   573  	// permitted. You may use the Roman alphabet in upper and lower case (A–Z, a–z),
   574  	// the digits 0 through 9, the dot (“.”), and the hyphen (“-”). This restriction
   575  	// is based on DNS name restrictions, set forth in RFC 1035.
   576  	//
   577  	// Uniform type identifiers may also contain any of the Unicode characters greater
   578  	// than U+007F.
   579  	//
   580  	// Note: the actual implementation of xcode does not allow some unicode characters
   581  	// greater than U+007f. In this implementation, we just replace everything non
   582  	// alphanumeric with "-" like the rfc1034identifier macro.
   583  	//
   584  	// * RFC1034 Label
   585  	//
   586  	// <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
   587  	// <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
   588  	// <let-dig-hyp> ::= <let-dig> | "-"
   589  	// <let-dig> ::= <letter> | <digit>
   590  	const surrSelf = 0x10000
   591  	begin := false
   592  
   593  	var res []rune
   594  	for i, r := range name {
   595  		if r == '.' && !begin {
   596  			continue
   597  		}
   598  		begin = true
   599  
   600  		switch {
   601  		case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z':
   602  			res = append(res, r)
   603  		case '0' <= r && r <= '9':
   604  			if i == 0 {
   605  				res = append(res, '-')
   606  			} else {
   607  				res = append(res, r)
   608  			}
   609  		default:
   610  			if r < surrSelf {
   611  				res = append(res, '-')
   612  			} else {
   613  				res = append(res, '-', '-')
   614  			}
   615  		}
   616  	}
   617  	return string(res)
   618  }