From a389fe699e164c5e0f0cd34a5f7707b4753ebd09 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 06:47:59 +0000 Subject: [PATCH 1/2] refactor: migrate build scripts and workflows to BuildShared Agent-Logs-Url: https://github.com/mpvkit/libbluray-build/sessions/5669a152-302e-4781-bcd8-7ed4f9725cf3 Co-authored-by: cxfksword <718792+cxfksword@users.noreply.github.com> --- .github/workflows/build.yml | 80 +- .github/workflows/check_version.yml | 65 +- .gitignore | 3 +- README.md | 2 + Sources/BuildScripts/Package.swift | 6 + .../BuildScripts/XCFrameworkBuild/base.swift | 1448 ----------------- .../BuildScripts/XCFrameworkBuild/main.swift | 30 +- docs/Package.template.swift | 9 +- 8 files changed, 44 insertions(+), 1599 deletions(-) delete mode 100644 Sources/BuildScripts/XCFrameworkBuild/base.swift diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5236ec4..4869032 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,78 +10,8 @@ on: jobs: build: - permissions: - contents: write - runs-on: macos-14 - steps: - - uses: actions/checkout@v4 - - - name: Install dependencies - run: | - brew install autoconf - brew install automake - brew install libtool - python -m pip install meson==1.4.2 - brew install ninja - - - name: Setup Xcode to support visionOS - run: | - sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer - xcodebuild -showsdks - - - - name: Check version to release - uses: jannekem/run-python-script-action@v1 - with: - script: | - import re - - def normalize_version(version_string): - version_string = re.sub(r'[^.0-9]+|-.+', '', version_string) - parts = re.split(r'\.', version_string) - major = int(parts[0]) - minor = int(parts[1]) if len(parts) > 1 else 0 - patch = int(parts[2]) if len(parts) > 2 else 0 - return f"{major}.{minor}.{patch}" - - file_path = './Sources/BuildScripts/XCFrameworkBuild/main.swift' - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - buildVersion = re.search(r'(case .libbluray[^"]+?)"(.+?)"', content).group(2) - print(f'build version: {buildVersion}') - releaseVersion = '${{ github.event.inputs.version }}' or normalize_version(buildVersion) - print(f'release version: {releaseVersion}') - set_env('BUILD_VERSION', buildVersion) - set_env('RELEASE_VERSION', releaseVersion) - - - name: Build - run: | - make build version=${{ env.RELEASE_VERSION }} - - - name: Update Package.swift - run: | - rm -rf ./Package.swift - cp -f ./dist/release/Package.swift ./Package.swift - - - name: Push Package.swift - uses: EndBug/add-and-commit@v9 - with: - default_author: github_actions - add: | - - Package.swift - message: "chore: bump version to ${{ env.BUILD_VERSION }}" - push: "origin HEAD:${{ github.ref_name }}" - - - name: Upload binary to GitHub Release - uses: softprops/action-gh-release@v2 - with: - name: ${{ contains(env.RELEASE_VERSION, '-') && env.RELEASE_VERSION || env.BUILD_VERSION }} - tag_name: ${{ env.RELEASE_VERSION }} - files: | - ./dist/release/*.txt - ./dist/release/*.zip - prerelease: ${{ contains(env.RELEASE_VERSION, '-') }} - generate_release_notes: true - fail_on_unmatched_files: true - body: | - https://code.videolan.org/videolan/libbluray/-/tree/${{ env.BUILD_VERSION }}?ref_type=tags + uses: mpvkit/BuildShared/.github/workflows/build.yml@main + secrets: inherit + with: + version: '${{ github.event.inputs.version }}' + library: 'libbluray' diff --git a/.github/workflows/check_version.yml b/.github/workflows/check_version.yml index 70d86d2..1ba5842 100644 --- a/.github/workflows/check_version.yml +++ b/.github/workflows/check_version.yml @@ -6,60 +6,11 @@ on: # - cron: '0 0 1 * *' # every month first day jobs: - build: - permissions: - contents: write - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Checkout latest code - run: | - git clone https://code.videolan.org/videolan/libbluray.git latest_code - - - name: Get latest verion - id: version - run: | - cd ./latest_code - latest_tag=$(git tag --sort=-v:refname | grep -v "-" | head -n 1) - echo "latest tag: $latest_tag" - echo "LATEST_VERSION=$latest_tag" >> $GITHUB_ENV - rm -rf ../latest_code - - - name: update to new version - uses: jannekem/run-python-script-action@v1 - with: - script: | - import re - - def parse_version(ver): - if '-' in ver or ver == '': - return 0 - return int(re.sub(r'[^0-9]+', r'', ver)) - - file_path = './Sources/BuildScripts/XCFrameworkBuild/main.swift' - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - oldVersion = re.search(r'(case .libbluray[^"]+?)"(.+?)"', content).group(2) - print("old version:", oldVersion) - set_env('OLD_VERSION', oldVersion) - newVersion = '${{ env.LATEST_VERSION }}' - print("new version:", newVersion) - if parse_version(newVersion) > parse_version(oldVersion): - content = re.sub(r'(case .libbluray[^"]+?)"(.+?)"', r'\1"${{ env.LATEST_VERSION }}"', content, count=1) - set_env('FOUND_NEW_VERSION', '1') - with open(file_path, 'w', encoding='utf-8') as file: - file.write(content) - - - - name: Create Pull Request - if: env.FOUND_NEW_VERSION - uses: peter-evans/create-pull-request@v6 - with: - add-paths: | - ./Sources/BuildScripts/XCFrameworkBuild/main.swift - title: "bump version to ${{ env.LATEST_VERSION }}" - body: | - https://code.videolan.org/videolan/libbluray/-/blob/${{ env.LATEST_VERSION }}/ChangeLog?ref_type=tags - commit-message: "chore: bump version to ${{ env.LATEST_VERSION }}" \ No newline at end of file + check_version: + uses: mpvkit/BuildShared/.github/workflows/check_version.yml@main + secrets: inherit + with: + repository: 'videolan/libbluray' + library: 'libbluray' + pr_body: | + {REPOSITORY_URL}/-/blob/{NEW_VERSION}/ChangeLog?ref_type=tags diff --git a/.gitignore b/.gitignore index d93bd02..25933e9 100644 --- a/.gitignore +++ b/.gitignore @@ -118,4 +118,5 @@ temp/ dist/ *.log *.xcframework -.vscode \ No newline at end of file +.vscode +Sources/BuildScripts/Package.resolved diff --git a/README.md b/README.md index b616812..b5d0ba7 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ build scripts for [libbluray](https://code.videolan.org/videolan/libbluray) +> This is a component of the [MPVKit](https://github.com/mpvkit/MPVKit) project. + ## Installation ### Swift Package Manager diff --git a/Sources/BuildScripts/Package.swift b/Sources/BuildScripts/Package.swift index 01180e3..e47fc7a 100644 --- a/Sources/BuildScripts/Package.swift +++ b/Sources/BuildScripts/Package.swift @@ -4,11 +4,17 @@ import PackageDescription let package = Package( name: "build", + platforms: [.macOS(.v11)], products: [ + .executable(name: "build", targets: ["build"]) + ], + dependencies: [ + .package(url: "https://github.com/mpvkit/BuildShared.git", branch: "main") ], targets: [ .executableTarget( name: "build", + dependencies: ["BuildShared"], path: "XCFrameworkBuild" ) ] diff --git a/Sources/BuildScripts/XCFrameworkBuild/base.swift b/Sources/BuildScripts/XCFrameworkBuild/base.swift deleted file mode 100644 index 1f48bb0..0000000 --- a/Sources/BuildScripts/XCFrameworkBuild/base.swift +++ /dev/null @@ -1,1448 +0,0 @@ -import Foundation - -enum Build { - static func performCommand(_ options: ArgumentOptions) throws { - if Utility.shell("which brew") == nil { - print(""" - You need to run the script first - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - """) - return - } - if Utility.shell("which pkg-config") == nil { - Utility.shell("brew install pkg-config") - } - if Utility.shell("which wget") == nil { - Utility.shell("brew install wget") - } - let path = URL.currentDirectory + "dist" - if !FileManager.default.fileExists(atPath: path.path) { - try? FileManager.default.createDirectory(at: path, withIntermediateDirectories: false, attributes: nil) - } - try? Utility.removeFiles(extensions: [".swift"], currentDirectoryURL: URL.currentDirectory + ["dist", "release"]) - FileManager.default.changeCurrentDirectoryPath(path.path) - BaseBuild.options = options - if !options.platforms.isEmpty { - BaseBuild.platforms = options.platforms - } - } -} - - -class ArgumentOptions { - private let arguments: [String] - var enableDebug: Bool = false - var enableSplitPlatform: Bool = false - var enableGPL: Bool = false - var platforms : [PlatformType] = [] - var releaseVersion: String = "0.0.0" - - init() { - self.arguments = [] - } - - init(arguments: [String]) { - self.arguments = arguments - } - - func contains(_ argument: String) -> Bool { - return self.arguments.firstIndex(of: argument) != nil - } - - static func parse(_ arguments: [String]) throws -> ArgumentOptions { - let options = ArgumentOptions(arguments: Array(arguments.dropFirst())) - for argument in arguments { - switch argument { - case "enable-debug": - options.enableDebug = true - case "enable-gpl": - options.enableGPL = true - case "enable-split-platform": - options.enableSplitPlatform = true - default: - if argument.hasPrefix("version=") { - let version = String(argument.suffix(argument.count - "version=".count)) - options.releaseVersion = version - } - if argument.hasPrefix("platform=") { - let values = String(argument.suffix(argument.count - "platform=".count)) - for val in values.split(separator: ",") { - let platformStr = val.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() - switch platformStr { - case "ios": - options.platforms += [PlatformType.ios, PlatformType.isimulator] - case "tvos": - options.platforms += [PlatformType.tvos, PlatformType.tvsimulator] - case "xros": - options.platforms += [PlatformType.xros, PlatformType.xrsimulator] - default: - guard let other = PlatformType(rawValue: platformStr) else { throw NSError(domain: "unknown platform: \(val)", code: 1) } - if !options.platforms.contains(other) { - options.platforms += [other] - } - } - } - } - } - } - - return options - } -} - -class BaseBuild { - static let defaultPath = "/Library/Frameworks/Python.framework/Versions/Current/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" - static var platforms = PlatformType.allCases - static var options = ArgumentOptions() - static let splitPlatformGroups = [ - PlatformType.macos.rawValue: [PlatformType.macos, PlatformType.maccatalyst], - PlatformType.ios.rawValue: [PlatformType.ios, PlatformType.isimulator], - PlatformType.tvos.rawValue: [PlatformType.tvos, PlatformType.tvsimulator], - PlatformType.xros.rawValue: [PlatformType.xros, PlatformType.xrsimulator] - ] - let library: Library - let directoryURL: URL - let xcframeworkDirectoryURL: URL - var pullLatestVersion = false; - init(library: Library) { - self.library = library - directoryURL = URL.currentDirectory + "\(library.rawValue)-\(library.version)" - xcframeworkDirectoryURL = URL.currentDirectory + ["release", "xcframework"] - } - - func beforeBuild() throws { - if FileManager.default.fileExists(atPath: directoryURL.path) { - return - } - - // pull code from git - if pullLatestVersion { - try! Utility.launch(path: "/usr/bin/git", arguments: ["-c", "advice.detachedHead=false", "clone", "--recursive", "--depth", "1", library.url, directoryURL.path]) - } else { - try! Utility.launch(path: "/usr/bin/git", arguments: ["-c", "advice.detachedHead=false", "clone", "--recursive", "--depth", "1", "--branch", library.version, library.url, directoryURL.path]) - } - - // apply patch - let patch = URL.currentDirectory + "../Sources/BuildScripts/patch/\(library.rawValue)" - if FileManager.default.fileExists(atPath: patch.path) { - _ = try? Utility.launch(path: "/usr/bin/git", arguments: ["checkout", "."], currentDirectoryURL: directoryURL) - let fileNames = try! FileManager.default.contentsOfDirectory(atPath: patch.path).sorted() - for fileName in fileNames { - if !fileName.hasSuffix(".patch") { - continue - } - try! Utility.launch(path: "/usr/bin/git", arguments: ["apply", "\((patch + fileName).path)"], currentDirectoryURL: directoryURL) - } - } - } - - func buildALL() throws { - try beforeBuild() - try? FileManager.default.removeItem(at: URL.currentDirectory + library.rawValue) - try? FileManager.default.removeItem(at: directoryURL.appendingPathExtension("log")) - for platform in BaseBuild.platforms { - for arch in architectures(platform) { - try build(platform: platform, arch: arch) - } - } - try createXCFramework() - try packageRelease() - try afterBuild() - } - - func afterBuild() throws { - try generatePackageManagerFile() - } - - func architectures(_ platform: PlatformType) -> [ArchType] { - platform.architectures - } - - func platforms() -> [PlatformType] { - BaseBuild.platforms - } - - func build(platform: PlatformType, arch: ArchType) throws { - let buildURL = scratch(platform: platform, arch: arch) - try? FileManager.default.createDirectory(at: buildURL, withIntermediateDirectories: true, attributes: nil) - let environ = environment(platform: platform, arch: arch) - if FileManager.default.fileExists(atPath: (directoryURL + "meson.build").path) { - if Utility.shell("which meson") == nil { - Utility.shell("brew install meson") - } - if Utility.shell("which ninja") == nil { - Utility.shell("brew install ninja") - } - - - let crossFile = createMesonCrossFile(platform: platform, arch: arch) - let meson = Utility.shell("which meson", isOutput: true)! - try Utility.launch(path: meson, arguments: ["setup", buildURL.path, "--cross-file=\(crossFile.path)"] + arguments(platform: platform, arch: arch), currentDirectoryURL: directoryURL, environment: environ) - try Utility.launch(path: meson, arguments: ["compile", "--clean"], currentDirectoryURL: buildURL, environment: environ) - try Utility.launch(path: meson, arguments: ["compile", "--verbose"], currentDirectoryURL: buildURL, environment: environ) - try Utility.launch(path: meson, arguments: ["install"], currentDirectoryURL: buildURL, environment: environ) - } else if FileManager.default.fileExists(atPath: (directoryURL + wafPath()).path) { - let waf = (directoryURL + wafPath()).path - try Utility.launch(path: waf, arguments: ["configure"] + arguments(platform: platform, arch: arch), currentDirectoryURL: directoryURL, environment: environ) - try Utility.launch(path: waf, arguments: wafBuildArg(), currentDirectoryURL: directoryURL, environment: environ) - try Utility.launch(path: waf, arguments: ["install"] + wafInstallArg(), currentDirectoryURL: directoryURL, environment: environ) - } else { - try configure(buildURL: buildURL, environ: environ, platform: platform, arch: arch) - try Utility.launch(path: "/usr/bin/make", arguments: ["-j8"], currentDirectoryURL: buildURL, environment: environ) - try Utility.launch(path: "/usr/bin/make", arguments: ["-j8", "install"], currentDirectoryURL: buildURL, environment: environ) - } - } - - func wafPath() -> String { - "./waf" - } - - func wafBuildArg() -> [String] { - ["build"] - } - - func wafInstallArg() -> [String] { - [] - } - - func configure(buildURL: URL, environ: [String: String], platform: PlatformType, arch: ArchType) throws { - let autogen = directoryURL + "autogen.sh" - if FileManager.default.fileExists(atPath: autogen.path) { - var environ = environ - environ["NOCONFIGURE"] = "1" - try Utility.launch(executableURL: autogen, arguments: [], currentDirectoryURL: directoryURL, environment: environ) - } - let makeLists = directoryURL + "CMakeLists.txt" - if FileManager.default.fileExists(atPath: makeLists.path) { - if Utility.shell("which cmake") == nil { - Utility.shell("brew install cmake") - } - let cmake = Utility.shell("which cmake", isOutput: true)! - let thinDirPath = thinDir(platform: platform, arch: arch).path - var arguments = [ - makeLists.path, - "-DCMAKE_VERBOSE_MAKEFILE=0", - "-DCMAKE_BUILD_TYPE=Release", - "-DCMAKE_OSX_SYSROOT=\(platform.sdk.lowercased())", - "-DCMAKE_OSX_ARCHITECTURES=\(arch.rawValue)", - "-DCMAKE_SYSTEM_NAME=\(platform.cmakeSystemName)", - "-DCMAKE_SYSTEM_PROCESSOR=\(arch.rawValue)", - "-DCMAKE_INSTALL_PREFIX=\(thinDirPath)", - "-DBUILD_SHARED_LIBS=0", - "-DCMAKE_POLICY_VERSION_MINIMUM=3.5", - ] - arguments.append(contentsOf: self.arguments(platform: platform, arch: arch)) - try Utility.launch(path: cmake, arguments: arguments, currentDirectoryURL: buildURL, environment: environ) - } else { - let configure = directoryURL + "configure" - if !FileManager.default.fileExists(atPath: configure.path) { - var bootstrap = directoryURL + "bootstrap" - if !FileManager.default.fileExists(atPath: bootstrap.path) { - bootstrap = directoryURL + ".bootstrap" - } - if FileManager.default.fileExists(atPath: bootstrap.path) { - try Utility.launch(executableURL: bootstrap, arguments: [], currentDirectoryURL: directoryURL, environment: environ) - } - } - var arguments = [ - "--prefix=\(thinDir(platform: platform, arch: arch).path)", - ] - arguments.append(contentsOf: self.arguments(platform: platform, arch: arch)) - try Utility.launch(executableURL: configure, arguments: arguments, currentDirectoryURL: buildURL, environment: environ) - } - } - - func environment(platform: PlatformType, arch: ArchType) -> [String: String] { - let cFlags = cFlags(platform: platform, arch: arch).joined(separator: " ") - let ldFlags = ldFlags(platform: platform, arch: arch).joined(separator: " ") - let pkgConfigPath = platform.pkgConfigPath(arch: arch) - let pkgConfigPathDefault = Utility.shell("pkg-config --variable pc_path pkg-config", isOutput: true)! - return [ - "LC_CTYPE": "C", - "CC": "/usr/bin/clang", - "CXX": "/usr/bin/clang++", - // "SDKROOT": platform.sdk.lowercased(), - "CURRENT_ARCH": arch.rawValue, - "CFLAGS": cFlags, - // makefile can't use CPPFLAGS - "CPPFLAGS": cFlags, - // 这个要加,不然cmake在编译maccatalyst 会有问题 - "CXXFLAGS": cFlags, - "ASMFLAGS": cFlags, - "LDFLAGS": ldFlags, - "PKG_CONFIG_LIBDIR": pkgConfigPath + pkgConfigPathDefault, - "PATH": BaseBuild.defaultPath, - ] - } - - func cFlags(platform: PlatformType, arch: ArchType) -> [String] { - var cFlags = platform.cFlags(arch: arch) - let librarys = flagsDependencelibrarys() - for library in librarys { - let path = URL.currentDirectory + [library.rawValue, platform.rawValue, "thin", arch.rawValue] - if FileManager.default.fileExists(atPath: path.path) { - cFlags.append("-I\(path.path)/include") - } - } - return cFlags - } - - func ldFlags(platform: PlatformType, arch: ArchType) -> [String] { - var ldFlags = platform.ldFlags(arch: arch) - let librarys = flagsDependencelibrarys() - for library in librarys { - let path = URL.currentDirectory + [library.rawValue, platform.rawValue, "thin", arch.rawValue] - if FileManager.default.fileExists(atPath: path.path) { - var libname = library.rawValue - if libname.hasPrefix("lib") { - libname = String(libname.dropFirst(3)) - } - ldFlags.append("-L\(path.path)/lib") - ldFlags.append("-l\(libname)") - } - } - return ldFlags - } - - func flagsDependencelibrarys() -> [Library] { - [] - } - - - func arguments(platform: PlatformType, arch: ArchType) -> [String] { - return [] - } - - func frameworks() throws -> [String] { - [library.rawValue] - } - - func createXCFramework() throws { - // clean all old xcframework - try? Utility.removeFiles(extensions: [".xcframework"], currentDirectoryURL: self.xcframeworkDirectoryURL) - - var frameworks: [String] = [] - let libNames = try self.frameworks() - for libName in libNames { - if libName.hasPrefix("lib") { - frameworks.append("Lib" + libName.dropFirst(3)) - } else { - frameworks.append(libName) - } - } - for framework in frameworks { - var frameworkGenerated = [PlatformType: String]() - for platform in BaseBuild.platforms { - if let frameworkPath = try createFramework(framework: framework, platform: platform) { - frameworkGenerated[platform] = frameworkPath - } - } - try buildXCFramework(name: framework, paths: Array(frameworkGenerated.values)) - - // Generate xcframework for different platforms - if BaseBuild.options.enableSplitPlatform { - for (group, platforms) in BaseBuild.splitPlatformGroups { - var frameworkPaths: [String] = [] - for platform in platforms { - if let frameworkPath = frameworkGenerated[platform] { - frameworkPaths.append(frameworkPath) - } - } - try buildXCFramework(name: "\(framework)-\(group)", paths: frameworkPaths) - } - } - } - } - - private func buildXCFramework(name: String, paths: [String]) throws { - if paths.isEmpty { - return - } - - var arguments = ["-create-xcframework"] - for frameworkPath in paths { - arguments.append("-framework") - arguments.append(frameworkPath) - } - arguments.append("-output") - let XCFrameworkFile = self.xcframeworkDirectoryURL + [name + ".xcframework"] - arguments.append(XCFrameworkFile.path) - if FileManager.default.fileExists(atPath: XCFrameworkFile.path) { - try? FileManager.default.removeItem(at: XCFrameworkFile) - } - try Utility.launch(path: "/usr/bin/xcodebuild", arguments: arguments) - } - - func createFramework(framework: String, platform: PlatformType) throws -> String? { - let platformDir = URL.currentDirectory + [library.rawValue, platform.rawValue] - if !FileManager.default.fileExists(atPath: platformDir.path) { - return nil - } - let frameworkDir = URL.currentDirectory + [library.rawValue, platform.rawValue, "\(framework).framework"] - if !platforms().contains(platform) { - if FileManager.default.fileExists(atPath: frameworkDir.path) { - return frameworkDir.path - } else { - return nil - } - } - try? FileManager.default.removeItem(at: frameworkDir) - try FileManager.default.createDirectory(at: frameworkDir, withIntermediateDirectories: true, attributes: nil) - var arguments = ["-create"] - for arch in platform.architectures { - let prefix = thinDir(platform: platform, arch: arch) - if !FileManager.default.fileExists(atPath: prefix.path) { - return nil - } - let libname = framework.hasPrefix("lib") || framework.hasPrefix("Lib") ? framework : "lib" + framework - var libPath = prefix + ["lib", "\(libname).a"] - if !FileManager.default.fileExists(atPath: libPath.path) { - libPath = prefix + ["lib", "\(libname).dylib"] - } - arguments.append(libPath.path) - var headerURL: URL = prefix + "include" + framework - if !FileManager.default.fileExists(atPath: headerURL.path) { - headerURL = prefix + "include" - } - try? FileManager.default.copyItem(at: headerURL, to: frameworkDir + "Headers") - } - arguments.append("-output") - arguments.append((frameworkDir + framework).path) - try Utility.launch(path: "/usr/bin/lipo", arguments: arguments) - try FileManager.default.createDirectory(at: frameworkDir + "Modules", withIntermediateDirectories: true, attributes: nil) - var modulemap = """ - framework module \(framework) [system] { - umbrella "." - - """ - frameworkExcludeHeaders(framework).forEach { header in - modulemap += """ - exclude header "\(header).h" - - """ - } - modulemap += """ - export * - } - """ - FileManager.default.createFile(atPath: frameworkDir.path + "/Modules/module.modulemap", contents: modulemap.data(using: .utf8), attributes: nil) - // Setting the minimum version to 100.0 is required for uploading a static framework to the App Store after Xcode 15.4 - // Fix: ITMS-90208: "Invalid Bundle. The bundle xxx.framework does not support the minimum OS Version specified in the Info.plist." - // It was originally using `platform.minVersion` - createPlist(path: frameworkDir.path + "/Info.plist", name: framework, minVersion: "100.0", platform: platform.sdk) - try fixShallowBundles(framework: framework, platform: platform, frameworkDir: frameworkDir) - return frameworkDir.path - } - - // Fix shallow bundles for Xcode 26, only for macOS frameworks - func fixShallowBundles(framework: String, platform: PlatformType, frameworkDir: URL) throws { - guard platform == .macos else { return } - - let infoPlistPath = frameworkDir + "Info.plist" - let versionsPath = frameworkDir + "Versions" - - // Check if this is a shallow bundle that needs fixing - var isDirectory: ObjCBool = false - let frameworkExists = FileManager.default.fileExists(atPath: frameworkDir.path, isDirectory: &isDirectory) - let hasInfoPlist = FileManager.default.fileExists(atPath: infoPlistPath.path) - let hasVersions = FileManager.default.fileExists(atPath: versionsPath.path, isDirectory: &isDirectory) && isDirectory.boolValue - - if frameworkExists && hasInfoPlist && !hasVersions { - print("Fixing \(framework).framework bundle structure...") - - // Create proper bundle structure - let versionAResourcesPath = frameworkDir + ["Versions", "A", "Resources"] - try FileManager.default.createDirectory(at: versionAResourcesPath, withIntermediateDirectories: true, attributes: nil) - - // Move Info.plist to proper location - let newInfoPlistPath = versionAResourcesPath + "Info.plist" - try FileManager.default.moveItem(at: infoPlistPath, to: newInfoPlistPath) - - // Move framework binary to proper location - let binaryPath = frameworkDir + framework - let newBinaryPath = frameworkDir + ["Versions", "A", framework] - if FileManager.default.fileExists(atPath: binaryPath.path) { - try FileManager.default.moveItem(at: binaryPath, to: newBinaryPath) - } - - // Move LICENSE if exists - let licensePath = frameworkDir + "LICENSE" - if FileManager.default.fileExists(atPath: licensePath.path) { - let newLicensePath = frameworkDir + ["Versions", "A", "LICENSE"] - try FileManager.default.moveItem(at: licensePath, to: newLicensePath) - } - - // Create symbolic links - let currentLinkPath = frameworkDir + ["Versions", "Current"] - try? FileManager.default.removeItem(at: currentLinkPath) - try FileManager.default.createSymbolicLink(atPath: currentLinkPath.path, withDestinationPath: "A") - - let binaryLinkPath = frameworkDir + framework - try? FileManager.default.removeItem(at: binaryLinkPath) - try FileManager.default.createSymbolicLink(atPath: binaryLinkPath.path, withDestinationPath: "Versions/Current/\(framework)") - - let resourcesLinkPath = frameworkDir + "Resources" - try? FileManager.default.removeItem(at: resourcesLinkPath) - try FileManager.default.createSymbolicLink(atPath: resourcesLinkPath.path, withDestinationPath: "Versions/Current/Resources") - - print("\(framework).framework structure fixed") - } - } - - func thinDir(library: Library, platform: PlatformType, arch: ArchType) -> URL { - URL.currentDirectory + [library.rawValue, platform.rawValue, "thin", arch.rawValue] - } - - func thinDir(platform: PlatformType, arch: ArchType) -> URL { - thinDir(library: library, platform: platform, arch: arch) - } - - func scratch(platform: PlatformType, arch: ArchType) -> URL { - URL.currentDirectory + [library.rawValue, platform.rawValue, "scratch", arch.rawValue] - } - - func frameworkExcludeHeaders(_: String) -> [String] { - [] - } - - private func createPlist(path: String, name: String, minVersion: String, platform: String) { - let identifier = "com.mpvkit." + normalizeBundleIdentifier(name) - let content = """ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - \(name) - CFBundleIdentifier - \(identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - \(name) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 87.88.520 - CFBundleVersion - 87.88.520 - CFBundleSignature - ???? - MinimumOSVersion - \(minVersion) - CFBundleSupportedPlatforms - - \(platform) - - NSPrincipalClass - - - - """ - FileManager.default.createFile(atPath: path, contents: content.data(using: .utf8), attributes: nil) - } - - // CFBundleIdentifier must contain only alphanumerics(a-z), dots(.), hyphens(-) - private func normalizeBundleIdentifier(_ identifier: String) -> String { - return identifier.replacingOccurrences(of: "_", with: "-") - } - - - private func createMesonCrossFile(platform: PlatformType, arch: ArchType) -> URL { - let url = scratch(platform: platform, arch: arch) - let crossFile = url + "crossFile.meson" - let prefix = thinDir(platform: platform, arch: arch) - let cFlags = cFlags(platform: platform, arch: arch).map { - "'" + $0 + "'" - }.joined(separator: ", ") - let ldFlags = ldFlags(platform: platform, arch: arch).map { - "'" + $0 + "'" - }.joined(separator: ", ") - let content = """ - [binaries] - c = '/usr/bin/clang' - cpp = '/usr/bin/clang++' - objc = '/usr/bin/clang' - objcpp = '/usr/bin/clang++' - ar = '\(platform.xcrunFind(tool: "ar"))' - strip = '\(platform.xcrunFind(tool: "strip"))' - pkg-config = 'pkg-config' - - [properties] - has_function_printf = true - has_function_hfkerhisadf = false - - [host_machine] - system = 'darwin' - subsystem = '\(platform.mesonSubSystem)' - kernel = 'xnu' - cpu_family = '\(arch.cpuFamily)' - cpu = '\(arch.targetCpu)' - endian = 'little' - - [built-in options] - default_library = 'static' - buildtype = 'release' - prefix = '\(prefix.path)' - c_args = [\(cFlags)] - cpp_args = [\(cFlags)] - objc_args = [\(cFlags)] - objcpp_args = [\(cFlags)] - c_link_args = [\(ldFlags)] - cpp_link_args = [\(ldFlags)] - objc_link_args = [\(ldFlags)] - objcpp_link_args = [\(ldFlags)] - """ - FileManager.default.createFile(atPath: crossFile.path, contents: content.data(using: .utf8), attributes: nil) - return crossFile - } - - func packageRelease() throws { - let releaseDirPath = URL.currentDirectory + ["release"] - if !FileManager.default.fileExists(atPath: releaseDirPath.path) { - try? FileManager.default.createDirectory(at: releaseDirPath, withIntermediateDirectories: true, attributes: nil) - } - let releaseLibPath = releaseDirPath + [library.rawValue] - try? FileManager.default.removeItem(at: releaseLibPath) - - // copy static libraries - for platform in BaseBuild.platforms { - for arch in architectures(platform) { - let thinLibPath = thinDir(platform: platform, arch: arch) + ["lib"] - if !FileManager.default.fileExists(atPath: thinLibPath.path) { - continue - } - let staticLibraries = try FileManager.default.contentsOfDirectory(atPath: thinLibPath.path).filter { $0.hasSuffix(".a") } - - let releaseThinLibPath = releaseDirPath + [library.rawValue, "lib", platform.rawValue, "thin", arch.rawValue, "lib"] - try? FileManager.default.createDirectory(at: releaseThinLibPath, withIntermediateDirectories: true, attributes: nil) - for lib in staticLibraries { - let sourceURL = thinLibPath + [lib] - let destinationURL = releaseThinLibPath + [lib] - try FileManager.default.copyItem(at: sourceURL, to: destinationURL) - } - } - } - - // copy includes - guard let firstPlatform = getFirstSuccessPlatform() else { return } - let firstArch = architectures(firstPlatform).first! - let includePath = thinDir(platform: firstPlatform, arch: firstArch) + ["include"] - let destIncludePath = releaseDirPath + [library.rawValue, "include"] - try FileManager.default.copyItem(at: includePath, to: destIncludePath) - - - // copy pkg-config file example - try packagePkgConfigRelease() - - // zip build artifacts when there are frameworks to generate - if try self.frameworks().count > 0 { - let sourceLib = releaseDirPath + [library.rawValue] - let destZipLibPath = releaseDirPath + [library.rawValue + "-all.zip"] - try? FileManager.default.removeItem(at: destZipLibPath) - try Utility.launch(path: "/usr/bin/zip", arguments: ["-qr", destZipLibPath.path, "./"], currentDirectoryURL: sourceLib) - } - - // zip xcframeworks - var frameworks: [String] = [] - let libNames = try self.frameworks() - for libName in libNames { - if libName.hasPrefix("lib") { - frameworks.append("Lib" + libName.dropFirst(3)) - } else { - frameworks.append(libName) - } - } - for framework in frameworks { - // clean old zip files - try? FileManager.default.removeItem(at: releaseDirPath + [framework + ".xcframework.zip"]) - try? FileManager.default.removeItem(at: releaseDirPath + [framework + ".xcframework.checksum.txt"]) - - let XCFrameworkFile = framework + ".xcframework" - let zipFile = releaseDirPath + [framework + ".xcframework.zip"] - let checksumFile = releaseDirPath + [framework + ".xcframework.checksum.txt"] - try Utility.launch(path: "/usr/bin/zip", arguments: ["-qry", zipFile.path, XCFrameworkFile], currentDirectoryURL: self.xcframeworkDirectoryURL) - Utility.shell("swift package compute-checksum \(zipFile.path) > \(checksumFile.path)") - - if BaseBuild.options.enableSplitPlatform { - for group in BaseBuild.splitPlatformGroups.keys { - let XCFrameworkName = "\(framework)-\(group)" - - // clean old zip files - try? FileManager.default.removeItem(at: releaseDirPath + [XCFrameworkName + ".xcframework.zip"]) - try? FileManager.default.removeItem(at: releaseDirPath + [XCFrameworkName + ".xcframework.checksum.txt"]) - - let XCFrameworkFile = XCFrameworkName + ".xcframework" - let XCFrameworkPath = self.xcframeworkDirectoryURL + ["\(framework)-\(group).xcframework"] - if FileManager.default.fileExists(atPath: XCFrameworkPath.path) { - let zipFile = releaseDirPath + [XCFrameworkName + ".xcframework.zip"] - let checksumFile = releaseDirPath + [XCFrameworkName + ".xcframework.checksum.txt"] - try Utility.launch(path: "/usr/bin/zip", arguments: ["-qry", zipFile.path, XCFrameworkFile], currentDirectoryURL: self.xcframeworkDirectoryURL) - Utility.shell("swift package compute-checksum \(zipFile.path) > \(checksumFile.path)") - } - } - } - } - } - - func packagePkgConfigRelease() throws { - let releaseDirPath = URL.currentDirectory + ["release"] - // copy pkg-config file example - for platform in BaseBuild.platforms { - for arch in architectures(platform) { - let thinLibPath = thinDir(platform: platform, arch: arch) + ["lib"] - let pkgconfigPath = thinLibPath + ["pkgconfig"] - if !FileManager.default.fileExists(atPath: pkgconfigPath.path) { - continue - } - let destPkgConfigDir = releaseDirPath + [library.rawValue, "pkgconfig-example", platform.rawValue] - let destPkgConfigPath = destPkgConfigDir + arch.rawValue - try? FileManager.default.createDirectory(at: destPkgConfigDir, withIntermediateDirectories: true, attributes: nil) - try FileManager.default.copyItem(at: pkgconfigPath, to: destPkgConfigPath) - - let pkgconfigFiles = Utility.listAllFiles(in: destPkgConfigPath) - for file in pkgconfigFiles { - if let data = FileManager.default.contents(atPath: file.path), var str = String(data: data, encoding: .utf8) { - str = str.replacingOccurrences(of: URL.currentDirectory.path, with: "/path/to/workdir") - try! str.write(toFile: file.path, atomically: true, encoding: .utf8) - } - } - } - } - } - - func generatePackageManagerFile() throws { - let releaseDirPath = URL.currentDirectory + ["release"] - let template = URL.currentDirectory + ["../docs/Package.template.swift"] - let packageFile = releaseDirPath + "Package.swift" - - if !FileManager.default.fileExists(atPath: packageFile.path) { - try! FileManager.default.createDirectory(at: releaseDirPath, withIntermediateDirectories: true, attributes: nil) - try! FileManager.default.copyItem(at: template, to: packageFile) - } - - var dependencyTargetContent = "" - if self is ZipBaseBuild { - for target in library.targets { - let tmpChecksum = FileManager.default.temporaryDirectory + "\(library.rawValue)_checksum.txt" - if FileManager.default.fileExists(atPath: tmpChecksum.path) { - try? FileManager.default.removeItem(at: tmpChecksum) - } - try! Utility.launch(path: "wget", arguments: ["-q", "-O", tmpChecksum.path, target.checksum], currentDirectoryURL: FileManager.default.temporaryDirectory) - let checksum = try String(contentsOf: tmpChecksum, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines) - dependencyTargetContent += """ - - .binaryTarget( - name: "\(target.name)", - url: "\(target.url)", - checksum: "\(checksum)" - ), - """ - try? FileManager.default.removeItem(at: tmpChecksum) - } - } else { - for target in library.targets { - let checksumFile = releaseDirPath + [target.name + ".xcframework.checksum.txt"] - let checksum = try String(contentsOf: checksumFile, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines) - dependencyTargetContent += """ - - .binaryTarget( - name: "\(target.name)", - url: "\(target.url)", - checksum: "\(checksum)" - ), - """ - } - } - - if dependencyTargetContent.isEmpty { - return - } - - if let data = FileManager.default.contents(atPath: packageFile.path), var str = String(data: data, encoding: .utf8) { - let placeholderChars = "//AUTO_GENERATE_TARGETS_END//" - str = str.replacingOccurrences(of: - """ - \(placeholderChars) - """, with: - """ - \(dependencyTargetContent) - \(placeholderChars) - """) - try! str.write(toFile: packageFile.path, atomically: true, encoding: .utf8) - } - } - - func getFirstSuccessPlatform() -> PlatformType? { - for platform in BaseBuild.platforms { - let firstArch = architectures(platform).first! - let thinPath = thinDir(platform: platform, arch: firstArch) - if FileManager.default.fileExists(atPath: thinPath.path) { - return platform - } - } - - return nil - } -} - -class CombineBaseBuild : BaseBuild { - - func combineFrameworkName() -> String { - "\(library.rawValue)-combined.a" - } - - func combineFrameworks(platform: PlatformType, arch: ArchType) -> [String] { - let thinLibPath = thinDir(platform: platform, arch: arch) + ["lib"] - let staticLibraries = try? FileManager.default.contentsOfDirectory(atPath: thinLibPath.path).filter { $0.hasSuffix(".a") } - guard let staticLibraries = staticLibraries else { - return [] - } - // order by create date descending - let sortedFrameworks = staticLibraries.sorted { - let file1Path = thinLibPath + [$0] - let file2Path = thinLibPath + [$1] - let attr1 = try? FileManager.default.attributesOfItem(atPath: file1Path.path) - let attr2 = try? FileManager.default.attributesOfItem(atPath: file2Path.path) - let date1 = attr1?[FileAttributeKey.creationDate] as? Date ?? Date.distantPast - let date2 = attr2?[FileAttributeKey.creationDate] as? Date ?? Date.distantPast - return date1 < date2 - } - return sortedFrameworks - } - - override func frameworks() throws -> [String] { - ["\(library.rawValue)-combined"] - } - - override func build(platform: PlatformType, arch: ArchType) throws { - try super.build(platform: platform, arch: arch) - - try combineStaticLibraries(platform: platform, arch: arch) - } - - func combineStaticLibraries(platform: PlatformType, arch: ArchType) throws { - let frameworks = self.combineFrameworks(platform: platform, arch: arch) - if frameworks.isEmpty { - return - } - - print("Create combine static libraries...") - let thinLibPath = thinDir(platform: platform, arch: arch) + ["lib"] - var combinedLibName = combineFrameworkName() - if !combinedLibName.hasSuffix(".a") { - combinedLibName += ".a" - } - var paths: [String] = [] - let prefix = thinDir(platform: platform, arch: arch) - if !FileManager.default.fileExists(atPath: prefix.path) { - throw NSError(domain: "no build for \(platform.rawValue) \(arch.rawValue)", code: 1) - } - for framework in frameworks { - let libname = framework.hasPrefix("lib") || framework.hasPrefix("Lib") ? framework : "lib" + framework - let libPath = prefix + ["lib", libname] - if !FileManager.default.fileExists(atPath: libPath.path) { - throw NSError(domain: "no library \(libPath.path) for \(platform.rawValue) \(arch.rawValue)", code: 1) - } - paths.append(libPath.path) - } - - let outputPath = prefix + ["lib", combinedLibName] - var arguments = ["-static"] - arguments.append(contentsOf: ["-o", outputPath.path]) - for frameworkPath in paths { - arguments.append(frameworkPath) - } - if FileManager.default.fileExists(atPath: outputPath.path) { - try? FileManager.default.removeItem(at: outputPath) - } - try Utility.launch(path: "/usr/bin/libtool", arguments: arguments) - - // move old static libraries to origin directory - let backupDirectory = thinLibPath + ["bak"] - try? FileManager.default.createDirectory(at: backupDirectory, withIntermediateDirectories: true, attributes: nil) - for framework in frameworks { - let libname = framework.hasPrefix("lib") || framework.hasPrefix("Lib") ? framework : "lib" + framework - let libPath = prefix + ["lib", libname] - let backupLibPath = backupDirectory + [libname] - try? FileManager.default.moveItem(at: libPath, to: backupLibPath) - } - - // create combine pkgconfig - let pkgconfigPath = thinLibPath + ["pkgconfig", "\(library.rawValue).pc"] - if !FileManager.default.fileExists(atPath: pkgconfigPath.path) { - throw NSError(domain: "no pkgconfig \(pkgconfigPath.path) for \(platform.rawValue) \(arch.rawValue)", code: 1) - } - - var content = try String(contentsOf: pkgconfigPath) - let combinedLibname = combinedLibName.hasPrefix("lib") ? String(combinedLibName.dropFirst(3).dropLast(2)) : String(combinedLibName.dropLast(2)) - content = content.replacingOccurrences( - of: "-L\\$\\{libdir\\}((\\s+-l\\S+)+)", - with: "-L${libdir} -l\(combinedLibname)", - options: .regularExpression - ) - - // move old pkgconfig to origin directory - let backupPkgconfigPath = backupDirectory + [pkgconfigPath.lastPathComponent] - try? FileManager.default.moveItem(at: pkgconfigPath, to: backupPkgconfigPath) - - // replace with combined pkgconfig - FileManager.default.createFile(atPath: pkgconfigPath.path, contents: content.data(using: .utf8), attributes: nil) - } - -} - -class ZipBaseBuild : BaseBuild { - - override func beforeBuild() throws { - // unzip builded static library - let outputFileName = "\(library.rawValue).zip" - let outputFile = directoryURL + outputFileName - // delete invalid downloaded files - let attributes = try? FileManager.default.attributesOfItem(atPath: outputFile.path) - if let fileSize = attributes?[FileAttributeKey.size] as? UInt64, fileSize <= 0 { - try? FileManager.default.removeItem(atPath: directoryURL.path) - } - try! FileManager.default.createDirectory(atPath: directoryURL.path, withIntermediateDirectories: true, attributes: nil) - - if !FileManager.default.fileExists(atPath: outputFile.path) { - try! Utility.launch(path: "wget", arguments: ["-O", outputFileName, library.url], currentDirectoryURL: directoryURL) - try! Utility.launch(path: "/usr/bin/unzip", arguments: ["-o",outputFileName], currentDirectoryURL: directoryURL) - } - } - - override func buildALL() throws { - try beforeBuild() - try? FileManager.default.removeItem(at: URL.currentDirectory + library.rawValue) - try? FileManager.default.removeItem(at: directoryURL.appendingPathExtension("log")) - try? FileManager.default.createDirectory(atPath: (URL.currentDirectory + library.rawValue).path, withIntermediateDirectories: true, attributes: nil) - for platform in BaseBuild.platforms { - for arch in architectures(platform) { - // restore lib - let srcThinLibPath = directoryURL + ["lib"] + [platform.rawValue, "thin", arch.rawValue, "lib"] - // ignore if platform not support - if !FileManager.default.fileExists(atPath: srcThinLibPath.path) { - continue - } - let destThinPath = thinDir(platform: platform, arch: arch) - let destThinLibPath = destThinPath + ["lib"] - try? FileManager.default.createDirectory(atPath: destThinPath.path, withIntermediateDirectories: true, attributes: nil) - try? FileManager.default.copyItem(at: srcThinLibPath, to: destThinLibPath) - - // restore include - let srcIncludePath = directoryURL + ["include"] - let destIncludePath = destThinPath + ["include"] - try? FileManager.default.copyItem(at: srcIncludePath, to: destIncludePath) - - // restore pkgconfig - let srcPkgConfigPath = directoryURL + ["pkgconfig-example", platform.rawValue, arch.rawValue] - let destPkgConfigPath = destThinPath + ["lib", "pkgconfig"] - try? FileManager.default.copyItem(at: srcPkgConfigPath, to: destPkgConfigPath) - Utility.listAllFiles(in: destPkgConfigPath).forEach { file in - if let data = FileManager.default.contents(atPath: file.path), var str = String(data: data, encoding: .utf8) { - str = str.replacingOccurrences(of: "/path/to/workdir", with: URL.currentDirectory.path) - try! str.write(toFile: file.path, atomically: true, encoding: .utf8) - } - } - } - } - - try afterBuild() - } - - override func afterBuild() throws { - try super.afterBuild() - } -} - -class PackageTarget { - let name: String - let url : String - let checksum: String - - init(name: String, url : String, checksum: String) { - self.name = name - self.url = url - self.checksum = checksum - } - - static func target( - name: String, - url : String, - checksum: String - ) -> PackageTarget { - return PackageTarget(name: name, url: url, checksum: checksum) - } -} - - -enum PlatformType: String, CaseIterable { - case xros, xrsimulator, maccatalyst, macos, isimulator, tvsimulator, tvos, ios - var minVersion: String { - switch self { - case .ios, .isimulator: - return "14.0" - case .tvos, .tvsimulator: - return "14.0" - case .macos: - return "11.0" - case .maccatalyst: - // return "14.0" - return "" - case .xros, .xrsimulator: - return "1.0" - } - } - - var name: String { - switch self { - case .ios, .tvos, .macos: - return rawValue - case .tvsimulator: - return "tvossim" - case .isimulator: - return "iossim" - case .maccatalyst: - return "maccat" - case .xros: - return "visionos" - case .xrsimulator: - return "visionossim" - } - } - - var frameworkName: String { - switch self { - case .ios: - return "ios-arm64" - case .maccatalyst: - return "ios-arm64_x86_64-maccatalyst" - case .isimulator: - return "ios-arm64_x86_64-simulator" - case .macos: - return "macos-arm64_x86_64" - case .tvos: - // 保持和xcode一致:https://github.com/KhronosGroup/MoltenVK/issues/431#issuecomment-771137085 - return "tvos-arm64_arm64e" - case .tvsimulator: - return "tvos-arm64_x86_64-simulator" - case .xros: - return "xros-arm64" - case .xrsimulator: - return "xros-arm64_x86_64-simulator" - } - } - - // xcodebuild default ARCHS = "$(ARCHS_STANDARD_64_BIT)" only build arm64e for tvos - var architectures: [ArchType] { - switch self { - case .ios, .xros: - return [.arm64] - case .tvos: - return [.arm64, .arm64e] - case .xrsimulator: - return [.arm64] - case .isimulator, .tvsimulator: - return [.arm64, .x86_64] - case .macos: - // macos 不能用arm64,不然打包release包会报错,不能通过 - #if arch(x86_64) - return [.x86_64, .arm64] - #else - return [.arm64, .x86_64] - #endif - case .maccatalyst: - return [.arm64, .x86_64] - } - } - - func deploymentTarget(_ arch: ArchType) -> String { - switch self { - case .ios, .tvos, .macos, .xros: - return "\(arch.targetCpu)-apple-\(rawValue)\(minVersion)" - case .maccatalyst: - return "\(arch.targetCpu)-apple-ios-macabi" - case .isimulator: - return PlatformType.ios.deploymentTarget(arch) + "-simulator" - case .tvsimulator: - return PlatformType.tvos.deploymentTarget(arch) + "-simulator" - case .xrsimulator: - return PlatformType.xros.deploymentTarget(arch) + "-simulator" - } - } - - - private var osVersionMin: String { - switch self { - case .ios, .tvos: - return "-m\(rawValue)-version-min=\(minVersion)" - case .macos: - return "-mmacosx-version-min=\(minVersion)" - case .isimulator: - return "-mios-simulator-version-min=\(minVersion)" - case .tvsimulator: - return "-mtvos-simulator-version-min=\(minVersion)" - case .maccatalyst, .xros, .xrsimulator: - return "" - // return "-miphoneos-version-min=\(minVersion)" - } - } - - var sdk : String { - switch self { - case .ios: - return "iPhoneOS" - case .isimulator: - return "iPhoneSimulator" - case .tvos: - return "AppleTVOS" - case .tvsimulator: - return "AppleTVSimulator" - case .macos: - return "MacOSX" - case .maccatalyst: - return "MacOSX" - case .xros: - return "XROS" - case .xrsimulator: - return "XRSimulator" - } - } - - var isysroot: String { - xcrunFind(tool: "--show-sdk-path") - } - - var mesonSubSystem: String { - switch self { - case .isimulator: - return "ios-simulator" - case .tvsimulator: - return "tvos-simulator" - case .xrsimulator: - return "xros-simulator" - default: - return rawValue - } - } - - var cmakeSystemName: String { - switch self { - case .ios, .isimulator: - return "iOS" - case .tvos, .tvsimulator: - return "tvOS" - case .macos, .maccatalyst: - return "Darwin" - case .xros, .xrsimulator: - return "visionOS" - } - } - - func host(arch: ArchType) -> String { - switch self { - case .ios, .isimulator, .maccatalyst: - return "\(arch == .x86_64 ? "x86_64" : "arm64")-ios-darwin" - case .tvos, .tvsimulator: - return "\(arch == .x86_64 ? "x86_64" : "arm64")-tvos-darwin" - case .xros, .xrsimulator: - return "\(arch == .x86_64 ? "x86_64" : "arm64")-xros-darwin" - case .macos: - return "\(arch == .x86_64 ? "x86_64" : "arm64")-apple-darwin" - } - } - - func ldFlags(arch: ArchType) -> [String] { - // ldFlags的关键参数要跟cFlags保持一致,不然会在ld的时候不通过。 - var flags = ["-lc++", "-arch", arch.rawValue, "-isysroot", isysroot, "-target", deploymentTarget(arch), osVersionMin] - // maccatalyst的vulkan库需要加载UIKit框架 - if self == .maccatalyst { - flags += ["-iframework", "\(isysroot)/System/iOSSupport/System/Library/Frameworks"] - } - return flags - } - - - func cFlags(arch: ArchType) -> [String] { - var cflags = ["-arch", arch.rawValue, "-isysroot", isysroot, "-target", deploymentTarget(arch), osVersionMin] -// if self == .macos || self == .maccatalyst { - // 不能同时有强符合和弱符号出现 - // cflags.append("-fno-common") -// } - if self == .tvos || self == .tvsimulator { - cflags.append("-DHAVE_FORK=0") - } - return cflags - } - - func xcrunFind(tool: String) -> String { - try! Utility.launch(path: "/usr/bin/xcrun", arguments: ["--sdk", sdk.lowercased(), "--find", tool], isOutput: true) - } - - func pkgConfigPath(arch: ArchType) -> String { - var pkgConfigPath = "" - for lib in Library.allCases { - let path = URL.currentDirectory + [lib.rawValue, rawValue, "thin", arch.rawValue] - if FileManager.default.fileExists(atPath: path.path) { - pkgConfigPath += "\(path.path)/lib/pkgconfig:" - } - } - return pkgConfigPath - } -} - -enum ArchType: String, CaseIterable { - // swiftlint:disable identifier_name - case arm64, x86_64, arm64e - // swiftlint:enable identifier_name - var executable: Bool { - guard let architecture = Bundle.main.executableArchitectures?.first?.intValue else { - return false - } - // NSBundleExecutableArchitectureARM64 - if architecture == 0x0100_000C, self == .arm64 { - return true - } else if architecture == NSBundleExecutableArchitectureX86_64, self == .x86_64 { - return true - } - return false - } - - - var cpuFamily: String { - switch self { - case .arm64, .arm64e: - return "aarch64" - case .x86_64: - return "x86_64" - } - } - - var targetCpu: String { - switch self { - case .arm64, .arm64e: - return "arm64" - case .x86_64: - return "x86_64" - } - } - - static var hostArch : ArchType { - #if arch(arm64) - return .arm64 - #else - return .x86_64 - #endif - } -} - - - -enum Utility { - @discardableResult - static func shell(_ command: String, isOutput : Bool = false, currentDirectoryURL: URL? = nil, environment: [String: String] = [:]) -> String? { - do { - return try launch(executableURL: URL(fileURLWithPath: "/bin/bash"), arguments: ["-c", command], isOutput: isOutput, currentDirectoryURL: currentDirectoryURL, environment: environment) - } catch { - print(error.localizedDescription) - return nil - } - } - - @discardableResult - static func launch(path: String, arguments: [String], isOutput: Bool = false, currentDirectoryURL: URL? = nil, environment: [String: String] = [:]) throws -> String { - if !path.hasPrefix("/") { - let execPath = Utility.shell("which \(path)", isOutput: true)! - if execPath.isEmpty { - throw NSError(domain: "[\(path)] not found", code: 1) - } - return try launch(executableURL: URL(fileURLWithPath: execPath), arguments: arguments, isOutput: isOutput, currentDirectoryURL: currentDirectoryURL, environment: environment) - } else { - return try launch(executableURL: URL(fileURLWithPath: path), arguments: arguments, isOutput: isOutput, currentDirectoryURL: currentDirectoryURL, environment: environment) - } - } - - @discardableResult - static func launch(executableURL: URL, arguments: [String], isOutput: Bool = false, currentDirectoryURL: URL? = nil, environment: [String: String] = [:]) throws -> String { - let task = Process() - var environment = environment - // for homebrew 1.12 - if ProcessInfo.processInfo.environment.keys.contains("HOME") { - environment["HOME"] = ProcessInfo.processInfo.environment["HOME"] - } - if !environment.keys.contains("PATH") { - environment["PATH"] = BaseBuild.defaultPath - } - task.environment = environment - - var outputFileHandle: FileHandle? - var logURL: URL? - var outputBuffer = Data() - let outputPipe = Pipe() - let errorPipe = Pipe() - task.standardOutput = outputPipe - task.standardError = errorPipe - - if let curURL = currentDirectoryURL { - // output to file - logURL = curURL.appendingPathExtension("log") - if !FileManager.default.fileExists(atPath: logURL!.path) { - FileManager.default.createFile(atPath: logURL!.path, contents: nil) - } - - outputFileHandle = try FileHandle(forWritingTo: logURL!) - outputFileHandle?.seekToEndOfFile() - } - outputPipe.fileHandleForReading.readabilityHandler = { fileHandle in - let data = fileHandle.availableData - - if !data.isEmpty { - outputBuffer.append(data) - if let outputString = String(data: data, encoding: .utf8) { - if isOutput { - print(outputString.trimmingCharacters(in: .newlines)) - } - - // Write to file simultaneously. - outputFileHandle?.write(data) - } - } else { - // Close the read capability processing program and clean up resources. - fileHandle.readabilityHandler = nil - fileHandle.closeFile() - } - } - errorPipe.fileHandleForReading.readabilityHandler = { fileHandle in - let data = fileHandle.availableData - - if !data.isEmpty { - if let outputString = String(data: data, encoding: .utf8) { - print(outputString.trimmingCharacters(in: .newlines)) - - // Write to file simultaneously. - outputFileHandle?.write(data) - } - } else { - // Close the read capability processing program and clean up resources. - fileHandle.readabilityHandler = nil - fileHandle.closeFile() - } - } - - task.arguments = arguments - var log = executableURL.path + " " + arguments.joined(separator: " ") + " environment: " + environment.description - if let currentDirectoryURL { - log += " url: \(currentDirectoryURL)" - } - print(log) - outputFileHandle?.write("\(log)\n".data(using: .utf8)!) - task.currentDirectoryURL = currentDirectoryURL - task.executableURL = executableURL - try task.run() - task.waitUntilExit() - if task.terminationStatus == 0 { - if isOutput { - let result = String(data: outputBuffer, encoding: .utf8)?.trimmingCharacters(in: .newlines) ?? "" - return result - } else { - return "" - } - } else { - if let logURL = logURL { - // print log when run in GitHub Action - if ProcessInfo.processInfo.environment.keys.contains("GITHUB_ACTION") { - // if build FFmpeg failed, print the ffbuild/config.log content - if logURL.path.contains("FFmpeg") { - let ffbuildLogURL = logURL - .deletingPathExtension() - .appendingPathComponent("ffbuild/config.log") - if FileManager.default.fileExists(atPath: ffbuildLogURL.path) { - if let content = String(data: try Data(contentsOf: ffbuildLogURL), encoding: .utf8) { - print("############# \(ffbuildLogURL) CONTENT BEGIN #############") - print(content) - print("############# \(ffbuildLogURL) CONTENT END #############") - } - } - } - - if let content = String(data: try Data(contentsOf: logURL), encoding: .utf8) { - print("############# \(logURL) CONTENT BEGIN #############") - print(content) - print("############# \(logURL) CONTENT END #############") - if #available(macOS 13.0, *) { - let regErrLogPath = try Regex("A full log can be found at\\s+?(/.*\\.txt)") - if let firstMatch = content.firstMatch(of: regErrLogPath) { - let errPath = "\(firstMatch[1].value ?? "")" - if !errPath.isEmpty { - print("############# \(errPath) CONTENT BEGIN #############") - let content = Utility.shell("cat \(errPath)", isOutput: true) - print(content ?? "") - print("############# \(errPath) CONTENT END #############") - } - } - } - } - - } - print("please view log file for detail: \(logURL)\n") - } - throw NSError(domain: "\(executableURL.lastPathComponent) execute failed", code: Int(task.terminationStatus)) - } - } - - @discardableResult - static func listAllFiles(in directory: URL) -> [URL] { - var allFiles: [URL] = [] - let enumerator = FileManager.default.enumerator(atPath: directory.path) - - while let file = enumerator?.nextObject() as? String { - let filePath = directory + [file] - var isDirectory: ObjCBool = false - - if FileManager.default.fileExists(atPath: filePath.path, isDirectory: &isDirectory) { - if isDirectory.boolValue { - // 如果是目录,则递归遍历该目录 - listAllFiles(in: filePath) - } else { - allFiles.append(filePath) - } - } - } - - return allFiles - } - - static func removeFiles(extensions: [String], currentDirectoryURL: URL) throws { - for ext in extensions { - let directoryContents = try FileManager.default.contentsOfDirectory(atPath: currentDirectoryURL.path) - for item in directoryContents { - if item.hasSuffix(ext) { - try FileManager.default.removeItem(at: currentDirectoryURL.appendingPathComponent(item)) - } - } - } - } -} - -extension URL { - static var currentDirectory: URL { - URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - } - - static func + (left: URL, right: String) -> URL { - var url = left - url.appendPathComponent(right) - return url - } - - static func + (left: URL, right: [String]) -> URL { - var url = left - right.forEach { - url.appendPathComponent($0) - } - return url - } -} - diff --git a/Sources/BuildScripts/XCFrameworkBuild/main.swift b/Sources/BuildScripts/XCFrameworkBuild/main.swift index 964b9bc..6acea70 100644 --- a/Sources/BuildScripts/XCFrameworkBuild/main.swift +++ b/Sources/BuildScripts/XCFrameworkBuild/main.swift @@ -1,19 +1,19 @@ import Foundation +import BuildShared do { - let options = try ArgumentOptions.parse(CommandLine.arguments) - try Build.performCommand(options) + let options = try BuildRunner.performCommand() - try BuildOpenSSL().buildALL() - try BuildFreetype().buildALL() - try BuildBluray().buildALL() + try BuildOpenSSL(options: options).buildALL() + try BuildFreetype(options: options).buildALL() + try BuildBluray(options: options).buildALL() } catch { - print("ERROR: \(error.localizedDescription)") + print(error.localizedDescription) exit(1) } -enum Library: String, CaseIterable { +enum Library: String, CaseIterable, BuildLibrary { case libbluray, openssl, libfreetype var version: String { switch self { @@ -44,8 +44,8 @@ enum Library: String, CaseIterable { return [ .target( name: "Libbluray", - url: "https://github.com/mpvkit/libbluray-build/releases/download/\(BaseBuild.options.releaseVersion)/Libbluray.xcframework.zip", - checksum: "https://github.com/mpvkit/libbluray-build/releases/download/\(BaseBuild.options.releaseVersion)/Libbluray.xcframework.checksum.txt" + url: "https://github.com/mpvkit/libbluray-build/releases/download/\(BuildRunner.options!.releaseVersion)/Libbluray.xcframework.zip", + checksum: "https://github.com/mpvkit/libbluray-build/releases/download/\(BuildRunner.options!.releaseVersion)/Libbluray.xcframework.checksum.txt" ), ] default: @@ -56,8 +56,8 @@ enum Library: String, CaseIterable { private class BuildBluray: BaseBuild { - init() { - super.init(library: .libbluray) + init(options: ArgumentOptions) { + super.init(library: Library.libbluray, options: options) // 只能 macos 支持 DiskArbitration 框架,其他平台需要注释去掉 DiskArbitration 依赖 } @@ -80,13 +80,13 @@ private class BuildBluray: BaseBuild { } private class BuildOpenSSL: ZipBaseBuild { - init() { - super.init(library: .openssl) + init(options: ArgumentOptions) { + super.init(library: Library.openssl, options: options) } } private class BuildFreetype: ZipBaseBuild { - init() { - super.init(library: .libfreetype) + init(options: ArgumentOptions) { + super.init(library: Library.libfreetype, options: options) } } diff --git a/docs/Package.template.swift b/docs/Package.template.swift index f44d552..023d94c 100644 --- a/docs/Package.template.swift +++ b/docs/Package.template.swift @@ -4,9 +4,12 @@ import PackageDescription let package = Package( name: "libbluray", - platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13)], + platforms: [.macOS(.v11), .iOS(.v14), .tvOS(.v14)], products: [ - .library(name: "Libbluray", targets: ["_Libbluray"]), + .library( + name: "Libbluray", + targets: ["_Libbluray"] + ), ], targets: [ // Need a dummy target to embedded correctly. @@ -19,4 +22,4 @@ let package = Package( //AUTO_GENERATE_TARGETS_BEGIN// //AUTO_GENERATE_TARGETS_END// ] -) \ No newline at end of file +) From 800fb33d3c95c5ac90326e52f32ca8a52f926057 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 06:50:40 +0000 Subject: [PATCH 2/2] chore: address workflow permissions and review feedback Agent-Logs-Url: https://github.com/mpvkit/libbluray-build/sessions/5669a152-302e-4781-bcd8-7ed4f9725cf3 Co-authored-by: cxfksword <718792+cxfksword@users.noreply.github.com> --- .github/workflows/build.yml | 2 ++ .github/workflows/check_version.yml | 3 +++ Sources/BuildScripts/XCFrameworkBuild/main.swift | 7 ++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4869032..14094e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,8 @@ on: jobs: build: + permissions: + contents: write uses: mpvkit/BuildShared/.github/workflows/build.yml@main secrets: inherit with: diff --git a/.github/workflows/check_version.yml b/.github/workflows/check_version.yml index 1ba5842..fae8eb9 100644 --- a/.github/workflows/check_version.yml +++ b/.github/workflows/check_version.yml @@ -7,6 +7,9 @@ on: jobs: check_version: + permissions: + contents: write + pull-requests: write uses: mpvkit/BuildShared/.github/workflows/check_version.yml@main secrets: inherit with: diff --git a/Sources/BuildScripts/XCFrameworkBuild/main.swift b/Sources/BuildScripts/XCFrameworkBuild/main.swift index 6acea70..018ff1c 100644 --- a/Sources/BuildScripts/XCFrameworkBuild/main.swift +++ b/Sources/BuildScripts/XCFrameworkBuild/main.swift @@ -8,7 +8,7 @@ do { try BuildFreetype(options: options).buildALL() try BuildBluray(options: options).buildALL() } catch { - print(error.localizedDescription) + print("ERROR: \(error.localizedDescription)") exit(1) } @@ -41,11 +41,12 @@ enum Library: String, CaseIterable, BuildLibrary { var targets : [PackageTarget] { switch self { case .libbluray: + let releaseVersion = BuildRunner.options?.releaseVersion ?? Library.libbluray.version return [ .target( name: "Libbluray", - url: "https://github.com/mpvkit/libbluray-build/releases/download/\(BuildRunner.options!.releaseVersion)/Libbluray.xcframework.zip", - checksum: "https://github.com/mpvkit/libbluray-build/releases/download/\(BuildRunner.options!.releaseVersion)/Libbluray.xcframework.checksum.txt" + url: "https://github.com/mpvkit/libbluray-build/releases/download/\(releaseVersion)/Libbluray.xcframework.zip", + checksum: "https://github.com/mpvkit/libbluray-build/releases/download/\(releaseVersion)/Libbluray.xcframework.checksum.txt" ), ] default: