Skip to content

Commit

Permalink
Merge pull request #1 from freddi-kit/first-commit
Browse files Browse the repository at this point in the history
First commit
  • Loading branch information
freddi-kit authored Dec 7, 2022
2 parents 81f4849 + 2f2afb0 commit 9708eca
Show file tree
Hide file tree
Showing 16 changed files with 479 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
31 changes: 31 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "ArtifactBundleGen",
products: [
.plugin(name: "ArtifactBundleGenCommand", targets: ["ArtifactBundleGenCommand"]),
.library(name: "ArtifactBundleGen", targets: ["ArtifactBundleGen"])
],
dependencies: [
],
targets: [
.target(
name: "ArtifactBundleGen",
dependencies: []),
.testTarget(
name: "ArtifactBundleGenTests",
dependencies: ["ArtifactBundleGen"]),
.plugin(
name: "ArtifactBundleGenCommand",
capability: .command(
intent: .custom(verb: "generate-artifact-bundle",
description: "Generate Artifact Bundle"),
permissions: [.writeToPackageDirectory(reason: "Save Artifact Bundle information")]
),
path: "Plugins/ArtifactBundleGenCommand"
),
]
)
32 changes: 32 additions & 0 deletions Plugins/ArtifactBundleGenCommand/ArtifactBundleGenCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import PackagePlugin
import Foundation
import OSLog

@main
struct ArtifactBundleGenCommand: CommandPlugin {

func performCommand(context: PluginContext, arguments: [String]) throws {
var argumentExtractor = ArgumentExtractor(arguments)

let packageVersionOption = argumentExtractor.extractOption(named: "package-version")
let packageNameOption = argumentExtractor.extractOption(named: "package-name")
let buildFolderNameOption = argumentExtractor.extractOption(named: "build-folder")
let configOption = argumentExtractor.extractOption(named: "build-config")

guard let name = packageNameOption.first else {
throw ArtifactBundleGenError.nameOptionMissing
}

guard let configString = configOption.first, let config = Config(rawValue: configString) else {
throw ArtifactBundleGenError.configOptionParseError(configString: configOption.first ?? "{empty}")
}

let artifactBundleGen = ArtifactBundleGen(
version: packageVersionOption.first ?? "1.0.0",
name: name,
buildFolderName: buildFolderNameOption.first ?? ".build",
config: config
)
try artifactBundleGen.generate()
}
}
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# ArtifactBundleGen (Beta)

Generates Artifact Bundle from Swift Package (Executable only)

## Example

```sh
# Go to Package directory. it is expeced that ArtifactBundleGen is added as command plugin in Package.swift
$ cd some-awesome-tool

# Call build command
$ swift build -c debug --arch arm64 --arch x86_64

# .build is generated

# Call command
$ swift package generate-artifact-bundle --package-version 0.5.11 --package-name some-awesome-tool --build-config debug --build-folder .build

$ ls
> some-awesome-tool.artifactbundle

# zip it and release!
$ zip -r some-awesome-tool.artifactbundle.zip some-awesome-tool.artifactbundle
```

## Usase
### 1. Add ArtifactBundleGen as plugin in Package.swift

```swift
let package = Package(
name: "XcodeGen",
products: [
// ... some products
],
dependencies: [
// ... some dependencies
.package(url: "https://github.com/freddi-kit/ArtifactBundleGen.git", .exact("0.0.1"))
],

```

### 2. Build your product in Shell

```sh
# Call build command
$ swift build -c debug --arch arm64 --arch x86_64
```


### 3. Call ArtifactBundleGen

```sh
$ swift package generate-artifact-bundle --package-version {version} --package-name {tool_name} --build-config {config} --build-folder {folder}
```

{tool_name}.artifactbundle will be generated!

#### Opitions
- --package-name: Name of Aritfact Bundle. Please specify executable tool's name
- --build-config: build config. `debug` or `release`

##### optionals
- --package-version: version of package (default is 1.0.0)
- --build-folder: version of package (default is .build)


### 4. Zip Artifact Bundle and release the zip!

```sh
$ zip -r {tool_name}.artifactbundle.zip {tool_name}.artifactbundle
```


## TODOs
- [ ] include LICENSE file
- [ ] Suport other type
137 changes: 137 additions & 0 deletions Sources/ArtifactBundleGen/ArtifactBundleGen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import Foundation

public struct ArtifactBundleGen {

private let version: String
private let name: String
private let buildFolderName: String
private let config: Config

private let folderCreator = FolderCreator()
private let lipoRunner = LipoRunnner()
private let fileCopy = FileCopy()
private let fileExistChecker = FileExistChecker()

private var artifactBundleFolderName: String {
"\(name).artifactbundle"
}

private var appleUniversalBinaryFolderName: String {
"\(buildFolderName)/apple/Products/\(config.headUpperCase)"
}

private var appleUniversalBinaryPath: String {
"\(appleUniversalBinaryFolderName)/xcodegen"
}

private func prepareArtifactBundleFolder() throws {
try folderCreator.createFolder(name: artifactBundleFolderName)
}

private func generateAppleUniversalUniversalBinaryArchIfExists() throws -> [Variant] {
guard fileExistChecker.isExist(path: appleUniversalBinaryPath) else { return [] }

var variants: [Variant] = []
let supoortedArchs = try lipoRunner.chechArch(of: appleUniversalBinaryPath)

let appBundleUniversalBinaryFolderName = "\(name)-\(version)-macosx"
let destinationUniversalBinaryFolderName = "\(artifactBundleFolderName)/\(appBundleUniversalBinaryFolderName)/bin"
try folderCreator.createFolder(name: destinationUniversalBinaryFolderName)

let destinationUniversalBinaryPath = "\(destinationUniversalBinaryFolderName)/\(name)"
let originExecutableURL = URL(fileURLWithPath: appleUniversalBinaryPath)
let destinationURL = URL(fileURLWithPath: destinationUniversalBinaryPath)

try fileCopy.copy(from: originExecutableURL, to: destinationURL)

variants.append(
Variant(
path: "\(appBundleUniversalBinaryFolderName)/bin/\(name)",
supportedTriples: supoortedArchs.map {
"\($0)-apple-macosx"
}
)
)

return variants
}

private func generateEachTriplesVariantsIfExists() throws -> [Variant] {
var variants: [Variant] = []

// check for all triples
for triple in VariantTriples.triples {
let executablePath = "\(buildFolderName)/\(triple)/\(config.rawValue)/\(name)"
guard fileExistChecker.isExist(path: executablePath) else { continue }

let artifactTripleDirectryPath = "\(artifactBundleFolderName)/\(triple)/bin"
try folderCreator.createFolder(name: artifactTripleDirectryPath)

let destinationPath = "\(artifactTripleDirectryPath)/\(name)"
let originExecutableURL = URL(fileURLWithPath: executablePath)
let destinationURL = URL(fileURLWithPath: destinationPath)

try fileCopy.copy(from: originExecutableURL, to: destinationURL)

variants.append(
Variant(
path: "\(triple)/bin/\(name)",
supportedTriples: [triple]
)
)
}

return variants
}

private func generateArtifactBundle(variants: [Variant]) -> ArtifactBundle {
let artifact = Artifact(
version: version,
type: .executable,
variants: variants
)

return ArtifactBundle(
schemaVersion: "1.0",
artifacts: [name: artifact]
)
}

private func encodeToJsonString(artifactBundle: ArtifactBundle) throws -> String {
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted

let jsonData = try jsonEncoder.encode(artifactBundle)

guard let string = String(data: jsonData, encoding: .utf8) else {
fatalError()
}
return string.replacingOccurrences(of: "\\", with: "")
}

public init(version: String, name: String, buildFolderName: String, config: Config) {
self.version = version
self.name = name
self.buildFolderName = buildFolderName
self.config = config
}

public func generate() throws {

try prepareArtifactBundleFolder()

var variants: [Variant] = []

let generatedAppleUniversalUniversalBinaryVariants = try generateAppleUniversalUniversalBinaryArchIfExists()
variants.append(contentsOf: generatedAppleUniversalUniversalBinaryVariants)

let generatedEachTriplesVariants = try generateEachTriplesVariantsIfExists()
variants.append(contentsOf: generatedEachTriplesVariants)

let artifactBundle = generateArtifactBundle(variants: variants)
let destinationPath = URL(fileURLWithPath: "\(artifactBundleFolderName)/info.json")

try encodeToJsonString(artifactBundle: artifactBundle)
.write(to: destinationPath, atomically: true, encoding: .utf8)
}
}
32 changes: 32 additions & 0 deletions Sources/ArtifactBundleGen/Error/ArtifactBundleGenError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// File.swift
//
//
// Created by JP29872 on 2022/12/07.
//

import Foundation

enum ArtifactBundleGenError: LocalizedError, CustomStringConvertible {
case folderCreationFailure(folderName: String, error: Error)
case fileCopyFailure(origin: String, destination: String, error: Error)

case lipoFailure(error: Error)
case lipoEmptyResult

case nameOptionMissing
case configOptionParseError(configString: String)

var description: String { return errorDescription ?? "Unexpected Error" }

var errorDescription: String? {
switch self {
case .folderCreationFailure(let folderName, let error): return "Failed to create folder named \"\(folderName)\" : \(error.localizedDescription)"
case .fileCopyFailure(let origin, let destination, let error): return "Failed to copy file from \"\(origin)\" to \"\(destination)\" : \(error.localizedDescription)"
case .lipoFailure(let error): return "Failed to run lipo: \(error.localizedDescription)"
case .lipoEmptyResult: return "Error, lipo returns empty result"
case .nameOptionMissing: return "Please specify to name by --package-name"
case .configOptionParseError(let configString): return "Failed to parse config specified by --build-config: \(configString)"
}
}
}
28 changes: 28 additions & 0 deletions Sources/ArtifactBundleGen/Model/ArtifactBundle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// ArtifactBundle.swift
//
//
// Created by JP29872 on 2022/12/06.
//

import Foundation

struct Variant: Encodable {
var path: String
var supportedTriples: [String]
}

struct Artifact: Encodable {
enum `Type`: String, Encodable {
case executable
}

var version: String
var type: `Type`
var variants: [Variant]
}

struct ArtifactBundle: Encodable {
var schemaVersion: String
var artifacts: [String: Artifact]
}
15 changes: 15 additions & 0 deletions Sources/ArtifactBundleGen/Model/Config.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

public enum Config: String, RawRepresentable, Encodable {
case debug
case release

var headUpperCase: String {
switch self {
case .debug:
return "Debug"
case .release:
return "Release"
}
}
}
Loading

0 comments on commit 9708eca

Please sign in to comment.