mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-07 14:25:17 +03:00
173 lines
5.9 KiB
Go
173 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/sagernet/sing-box/log"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
|
|
"howett.net/plist"
|
|
)
|
|
|
|
type xcFrameworkInfo struct {
|
|
AvailableLibraries []xcFrameworkLibrary `plist:"AvailableLibraries"`
|
|
}
|
|
|
|
type xcFrameworkLibrary struct {
|
|
BinaryPath string `plist:"BinaryPath"`
|
|
LibraryIdentifier string `plist:"LibraryIdentifier"`
|
|
LibraryPath string `plist:"LibraryPath"`
|
|
SupportedArchitectures []string `plist:"SupportedArchitectures"`
|
|
SupportedPlatform string `plist:"SupportedPlatform"`
|
|
SupportedPlatformVariant string `plist:"SupportedPlatformVariant"`
|
|
}
|
|
|
|
type frameworkSlice struct {
|
|
rootPath string
|
|
library xcFrameworkLibrary
|
|
}
|
|
|
|
var outputPath string
|
|
|
|
func init() {
|
|
flag.StringVar(&outputPath, "output", "", "output XCFramework path")
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
err := merge()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func merge() error {
|
|
inputPaths := flag.Args()
|
|
if outputPath == "" {
|
|
return E.New("missing output path")
|
|
}
|
|
if len(inputPaths) == 0 {
|
|
return E.New("missing input XCFramework paths")
|
|
}
|
|
frameworkGroups := make(map[string][]frameworkSlice)
|
|
for _, inputPath := range inputPaths {
|
|
infoFile, err := os.Open(filepath.Join(inputPath, "Info.plist"))
|
|
if err != nil {
|
|
return E.Cause(err, "open XCFramework metadata: ", inputPath)
|
|
}
|
|
var info xcFrameworkInfo
|
|
decoder := plist.NewDecoder(infoFile)
|
|
err = decoder.Decode(&info)
|
|
closeErr := infoFile.Close()
|
|
if err != nil {
|
|
return E.Cause(err, "decode XCFramework metadata: ", inputPath)
|
|
}
|
|
if closeErr != nil {
|
|
return E.Cause(closeErr, "close XCFramework metadata: ", inputPath)
|
|
}
|
|
for _, library := range info.AvailableLibraries {
|
|
groupName := library.SupportedPlatform + "|" + library.SupportedPlatformVariant
|
|
frameworkGroups[groupName] = append(frameworkGroups[groupName], frameworkSlice{
|
|
rootPath: inputPath,
|
|
library: library,
|
|
})
|
|
}
|
|
}
|
|
groupNames := make([]string, 0, len(frameworkGroups))
|
|
for groupName := range frameworkGroups {
|
|
groupNames = append(groupNames, groupName)
|
|
}
|
|
sort.Strings(groupNames)
|
|
absoluteOutputPath, err := filepath.Abs(outputPath)
|
|
if err != nil {
|
|
return E.Cause(err, "resolve output XCFramework path")
|
|
}
|
|
err = os.MkdirAll(filepath.Dir(absoluteOutputPath), 0o755)
|
|
if err != nil {
|
|
return E.Cause(err, "create output XCFramework directory")
|
|
}
|
|
temporaryDirectory, err := os.MkdirTemp(filepath.Dir(absoluteOutputPath), ".merge-xcframework-*")
|
|
if err != nil {
|
|
return E.Cause(err, "create XCFramework merge directory")
|
|
}
|
|
defer os.RemoveAll(temporaryDirectory)
|
|
frameworkPaths := make([]string, 0, len(groupNames))
|
|
for groupIndex, groupName := range groupNames {
|
|
frameworkSlices := frameworkGroups[groupName]
|
|
firstSlice := frameworkSlices[0]
|
|
firstFrameworkPath := filepath.Join(firstSlice.rootPath, firstSlice.library.LibraryIdentifier, firstSlice.library.LibraryPath)
|
|
if len(frameworkSlices) == 1 {
|
|
frameworkPaths = append(frameworkPaths, firstFrameworkPath)
|
|
continue
|
|
}
|
|
architectures := make(map[string]bool)
|
|
binaryPaths := make([]string, 0, len(frameworkSlices))
|
|
for _, currentSlice := range frameworkSlices {
|
|
if currentSlice.library.LibraryPath != firstSlice.library.LibraryPath || currentSlice.library.BinaryPath != firstSlice.library.BinaryPath {
|
|
return E.New("incompatible XCFramework slices for platform: ", currentSlice.library.SupportedPlatform)
|
|
}
|
|
for _, architecture := range currentSlice.library.SupportedArchitectures {
|
|
if architectures[architecture] {
|
|
return E.New("duplicate XCFramework architecture: ", architecture)
|
|
}
|
|
architectures[architecture] = true
|
|
}
|
|
binaryPaths = append(binaryPaths, filepath.Join(currentSlice.rootPath, currentSlice.library.LibraryIdentifier, currentSlice.library.BinaryPath))
|
|
}
|
|
mergedFrameworkPath := filepath.Join(temporaryDirectory, "framework-"+strconv.Itoa(groupIndex), filepath.Base(firstSlice.library.LibraryPath))
|
|
copyCommand := exec.Command("ditto", firstFrameworkPath, mergedFrameworkPath)
|
|
copyCommand.Stdout = os.Stdout
|
|
copyCommand.Stderr = os.Stderr
|
|
err = copyCommand.Run()
|
|
if err != nil {
|
|
return E.Cause(err, "copy XCFramework slice")
|
|
}
|
|
binaryRelativePath, relativeErr := filepath.Rel(firstSlice.library.LibraryPath, firstSlice.library.BinaryPath)
|
|
if relativeErr != nil {
|
|
return E.Cause(relativeErr, "resolve XCFramework binary path")
|
|
}
|
|
if binaryRelativePath == "." || strings.HasPrefix(binaryRelativePath, ".."+string(filepath.Separator)) {
|
|
return E.New("invalid XCFramework binary path: ", firstSlice.library.BinaryPath)
|
|
}
|
|
mergedBinaryPath := filepath.Join(mergedFrameworkPath, binaryRelativePath)
|
|
temporaryBinaryPath := mergedBinaryPath + ".merged"
|
|
lipoArguments := append([]string{"lipo", "-create"}, binaryPaths...)
|
|
lipoArguments = append(lipoArguments, "-output", temporaryBinaryPath)
|
|
lipoCommand := exec.Command("xcrun", lipoArguments...)
|
|
lipoCommand.Stdout = os.Stdout
|
|
lipoCommand.Stderr = os.Stderr
|
|
err = lipoCommand.Run()
|
|
if err != nil {
|
|
return E.Cause(err, "merge XCFramework binaries")
|
|
}
|
|
err = os.Rename(temporaryBinaryPath, mergedBinaryPath)
|
|
if err != nil {
|
|
return E.Cause(err, "replace merged XCFramework binary")
|
|
}
|
|
frameworkPaths = append(frameworkPaths, mergedFrameworkPath)
|
|
}
|
|
err = os.RemoveAll(absoluteOutputPath)
|
|
if err != nil {
|
|
return E.Cause(err, "remove output XCFramework")
|
|
}
|
|
xcodebuildArguments := []string{"-create-xcframework"}
|
|
for _, frameworkPath := range frameworkPaths {
|
|
xcodebuildArguments = append(xcodebuildArguments, "-framework", frameworkPath)
|
|
}
|
|
xcodebuildArguments = append(xcodebuildArguments, "-output", absoluteOutputPath)
|
|
xcodebuildCommand := exec.Command("xcodebuild", xcodebuildArguments...)
|
|
xcodebuildCommand.Stdout = os.Stdout
|
|
xcodebuildCommand.Stderr = os.Stderr
|
|
err = xcodebuildCommand.Run()
|
|
if err != nil {
|
|
return E.Cause(err, "create XCFramework")
|
|
}
|
|
return nil
|
|
}
|