mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-07 06:15:15 +03:00
release: Backport workflow refactor
This commit is contained in:
776
.github/workflows/build.yml
vendored
776
.github/workflows/build.yml
vendored
File diff suppressed because it is too large
Load Diff
@@ -289,7 +289,7 @@ func prepareAppStore(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(builds.Data) == 0 {
|
if len(builds.Data) == 0 {
|
||||||
log.Fatal(platform, " ", tag, " no build found")
|
log.Fatal(string(platform), " ", tag, " no build found")
|
||||||
}
|
}
|
||||||
buildID := common.Ptr(builds.Data[0].ID)
|
buildID := common.Ptr(builds.Data[0].ID)
|
||||||
if version.ID == "" {
|
if version.ID == "" {
|
||||||
|
|||||||
165
cmd/internal/merge_aar/main.go
Normal file
165
cmd/internal/merge_aar/main.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"crypto/sha256"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var outputPath string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&outputPath, "output", "", "output AAR 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 AAR paths")
|
||||||
|
}
|
||||||
|
archiveReaders := make([]*zip.ReadCloser, 0, len(inputPaths))
|
||||||
|
for _, inputPath := range inputPaths {
|
||||||
|
archiveReader, err := zip.OpenReader(inputPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open input AAR: ", inputPath)
|
||||||
|
}
|
||||||
|
archiveReaders = append(archiveReaders, archiveReader)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
for _, archiveReader := range archiveReaders {
|
||||||
|
archiveReader.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
referenceEntries := make(map[string][sha256.Size]byte)
|
||||||
|
selectedEntries := make([]*zip.File, 0)
|
||||||
|
selectedJNIEntries := make(map[string]bool)
|
||||||
|
for inputIndex, archiveReader := range archiveReaders {
|
||||||
|
seenEntries := make(map[string]bool)
|
||||||
|
for _, archiveFile := range archiveReader.File {
|
||||||
|
if strings.HasPrefix(archiveFile.Name, "jni/") {
|
||||||
|
if archiveFile.FileInfo().IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if selectedJNIEntries[archiveFile.Name] {
|
||||||
|
return E.New("duplicate AAR JNI entry: ", archiveFile.Name)
|
||||||
|
}
|
||||||
|
selectedJNIEntries[archiveFile.Name] = true
|
||||||
|
selectedEntries = append(selectedEntries, archiveFile)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entryDigest, err := digestEntry(archiveFile)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read AAR entry: ", archiveFile.Name)
|
||||||
|
}
|
||||||
|
if inputIndex == 0 {
|
||||||
|
referenceEntries[archiveFile.Name] = entryDigest
|
||||||
|
selectedEntries = append(selectedEntries, archiveFile)
|
||||||
|
} else {
|
||||||
|
referenceDigest, loaded := referenceEntries[archiveFile.Name]
|
||||||
|
if !loaded {
|
||||||
|
return E.New("unexpected AAR entry: ", archiveFile.Name)
|
||||||
|
}
|
||||||
|
if referenceDigest != entryDigest {
|
||||||
|
return E.New("AAR entry differs between architectures: ", archiveFile.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seenEntries[archiveFile.Name] = true
|
||||||
|
}
|
||||||
|
if inputIndex > 0 {
|
||||||
|
for referenceName := range referenceEntries {
|
||||||
|
if !seenEntries[referenceName] {
|
||||||
|
return E.New("missing AAR entry: ", referenceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absoluteOutputPath, err := filepath.Abs(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "resolve output AAR path")
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(filepath.Dir(absoluteOutputPath), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "create output AAR directory")
|
||||||
|
}
|
||||||
|
temporaryFile, err := os.CreateTemp(filepath.Dir(absoluteOutputPath), ".merge-aar-*.aar")
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "create temporary output AAR")
|
||||||
|
}
|
||||||
|
temporaryPath := temporaryFile.Name()
|
||||||
|
defer os.Remove(temporaryPath)
|
||||||
|
archiveWriter := zip.NewWriter(temporaryFile)
|
||||||
|
for _, archiveFile := range selectedEntries {
|
||||||
|
rawReader, openErr := archiveFile.OpenRaw()
|
||||||
|
if openErr != nil {
|
||||||
|
archiveWriter.Close()
|
||||||
|
temporaryFile.Close()
|
||||||
|
return E.Cause(openErr, "open raw AAR entry: ", archiveFile.Name)
|
||||||
|
}
|
||||||
|
header := archiveFile.FileHeader
|
||||||
|
rawWriter, createErr := archiveWriter.CreateRaw(&header)
|
||||||
|
if createErr != nil {
|
||||||
|
archiveWriter.Close()
|
||||||
|
temporaryFile.Close()
|
||||||
|
return E.Cause(createErr, "create output AAR entry: ", archiveFile.Name)
|
||||||
|
}
|
||||||
|
_, copyErr := io.Copy(rawWriter, rawReader)
|
||||||
|
if copyErr != nil {
|
||||||
|
archiveWriter.Close()
|
||||||
|
temporaryFile.Close()
|
||||||
|
return E.Cause(copyErr, "copy output AAR entry: ", archiveFile.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = archiveWriter.Close()
|
||||||
|
if err != nil {
|
||||||
|
temporaryFile.Close()
|
||||||
|
return E.Cause(err, "finalize output AAR")
|
||||||
|
}
|
||||||
|
err = temporaryFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close output AAR")
|
||||||
|
}
|
||||||
|
err = os.Rename(temporaryPath, absoluteOutputPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "replace output AAR")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func digestEntry(archiveFile *zip.File) ([sha256.Size]byte, error) {
|
||||||
|
entryReader, err := archiveFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
return [sha256.Size]byte{}, err
|
||||||
|
}
|
||||||
|
digest := sha256.New()
|
||||||
|
_, err = io.Copy(digest, entryReader)
|
||||||
|
closeErr := entryReader.Close()
|
||||||
|
if err != nil {
|
||||||
|
return [sha256.Size]byte{}, err
|
||||||
|
}
|
||||||
|
if closeErr != nil {
|
||||||
|
return [sha256.Size]byte{}, closeErr
|
||||||
|
}
|
||||||
|
var result [sha256.Size]byte
|
||||||
|
copy(result[:], digest.Sum(nil))
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
172
cmd/internal/merge_apple_xcframework/main.go
Normal file
172
cmd/internal/merge_apple_xcframework/main.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -106,6 +106,7 @@ func findAndReplaceProjectVersion(objectsMap map[string]any, projectContent stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
||||||
|
globalSettings := collectBuildSettings(objectsMap)
|
||||||
var objectKeyList []string
|
var objectKeyList []string
|
||||||
for objectKey, object := range objectsMap {
|
for objectKey, object := range objectsMap {
|
||||||
buildSettings := object.(map[string]any)["buildSettings"]
|
buildSettings := object.(map[string]any)["buildSettings"]
|
||||||
@@ -116,13 +117,51 @@ func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
|||||||
if bundleIDObject == nil {
|
if bundleIDObject == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if common.Contains(bundleIDList, bundleIDObject.(string)) {
|
bundleID := expandBuildVariables(bundleIDObject.(string), globalSettings)
|
||||||
|
if common.Contains(bundleIDList, bundleID) {
|
||||||
objectKeyList = append(objectKeyList, objectKey)
|
objectKeyList = append(objectKeyList, objectKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return objectKeyList
|
return objectKeyList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectBuildSettings(objectsMap map[string]any) map[string]string {
|
||||||
|
settings := make(map[string]string)
|
||||||
|
for _, object := range objectsMap {
|
||||||
|
buildSettings, loaded := object.(map[string]any)["buildSettings"].(map[string]any)
|
||||||
|
if !loaded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for key, value := range buildSettings {
|
||||||
|
valueString, isString := value.(string)
|
||||||
|
if !isString {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
settings[key] = valueString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildVariableRegexp = regexp.MustCompile(`\$[({]([A-Za-z0-9_]+)[)}]`)
|
||||||
|
|
||||||
|
func expandBuildVariables(value string, settings map[string]string) string {
|
||||||
|
for {
|
||||||
|
expanded := buildVariableRegexp.ReplaceAllStringFunc(value, func(match string) string {
|
||||||
|
name := buildVariableRegexp.FindStringSubmatch(match)[1]
|
||||||
|
replacement, loaded := settings[name]
|
||||||
|
if !loaded {
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
return replacement
|
||||||
|
})
|
||||||
|
if expanded == value {
|
||||||
|
return expanded
|
||||||
|
}
|
||||||
|
value = expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
|
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
|
||||||
var objectKeyList []string
|
var objectKeyList []string
|
||||||
for objectKey, object := range objectsMap {
|
for objectKey, object := range objectsMap {
|
||||||
|
|||||||
Reference in New Issue
Block a user