build_apple.go 17 KB

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