-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added: command plugin to download model files. #79
Open
Arpit160399
wants to merge
2
commits into
soto-project:main
Choose a base branch
from
Arpit160399:ModelDownloder-Plugin
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+443
−1
Open
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// swift-tools-version: 5.9 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "soto-codegenerator", | ||
platforms: [.macOS(.v10_15)], | ||
products: [ | ||
.executable(name: "SotoCodeGenerator", targets: ["SotoCodeGenerator"]), | ||
.plugin(name: "SotoCodeGeneratorPlugin", targets: ["SotoCodeGeneratorPlugin"]), | ||
.plugin(name: "SotoCodeModelDownloaderPlugin",targets: ["SotoCodeModelDownloaderPlugin"]) | ||
], | ||
dependencies: [ | ||
.package(url: "https://github.com/soto-project/soto-smithy.git", from: "0.3.1"), | ||
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"), | ||
.package(url: "https://github.com/hummingbird-project/hummingbird-mustache.git", from: "1.0.3"), | ||
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"), | ||
], | ||
targets: [ | ||
.executableTarget( | ||
name: "SotoCodeGenerator", | ||
dependencies: [ | ||
.byName(name: "SotoCodeGeneratorLib"), | ||
.product(name: "ArgumentParser", package: "swift-argument-parser"), | ||
.product(name: "Logging", package: "swift-log") | ||
] | ||
), | ||
.target( | ||
name: "SotoCodeGeneratorLib", | ||
dependencies: [ | ||
.product(name: "SotoSmithy", package: "soto-smithy"), | ||
.product(name: "SotoSmithyAWS", package: "soto-smithy"), | ||
.product(name: "HummingbirdMustache", package: "hummingbird-mustache"), | ||
.product(name: "Logging", package: "swift-log") | ||
] | ||
), | ||
.plugin( | ||
name: "SotoCodeGeneratorPlugin", | ||
capability: .buildTool(), | ||
dependencies: ["SotoCodeGenerator"] | ||
), | ||
.plugin( | ||
name: "SotoCodeModelDownloaderPlugin", | ||
capability: .command( | ||
intent: .custom( | ||
verb: "get-soto-models", | ||
description: "Download the required Model file schema required for soto code genrator to work"), | ||
permissions: [ | ||
.writeToPackageDirectory(reason: "Write the Model files into target project"), | ||
.allowNetworkConnections( | ||
scope: .all(ports: []), | ||
reason: "The plugin needs to download resource's from remote server" | ||
) | ||
]) | ||
), | ||
.testTarget( | ||
name: "SotoCodeGeneratorTests", | ||
dependencies: ["SotoCodeGeneratorLib"] | ||
) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Soto for AWS open source project | ||
// | ||
// Copyright (c) 2017-2023 the Soto project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of Soto project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import Foundation | ||
/// Downloads file from GitHub repositories. | ||
struct GitHubResource { | ||
|
||
/// The input folder containing the GitHub repository URL. | ||
var inputFolder: String | ||
|
||
/// The output folder where the downloaded files will be saved. | ||
var outputFolder: String | ||
|
||
/// An array of expected services to be downloaded. | ||
var expectedServices: [String] = [] | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// The base URL for the GitHub API. | ||
static var gitApi: URL { URL(string: "https://api.github.com/repos")! } | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// trigger Downloading files from the GitHub repository. | ||
func download() async throws { | ||
|
||
// Ensure that the input folder contains a GitHub repository URL. | ||
guard inputFolder.contains("github.com") else { return } | ||
guard let gitFolderUrl = URL(string: inputFolder) else { return } | ||
guard let (user, repo, directory, ref) = try extractGitComponents(from: gitFolderUrl.absoluteString) else { return } | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Fetch the list of files from the GitHub repository. | ||
let files = try await gitTreesApi(user: user, repository: repo, directory: directory) | ||
|
||
// Create the output folder if it does not exist. | ||
if !FileManager.default.fileExists(atPath: outputFolder) { | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
try FileManager.default.createDirectory(atPath: outputFolder, withIntermediateDirectories: true) | ||
} | ||
|
||
// Download files concurrently using a task group. | ||
try await withThrowingTaskGroup(of: Void.self) { group in | ||
for index in 0..<files.count { | ||
group.addTask { | ||
if let filepath = files[index] as? String { | ||
let escapingPath = filepath.replacingOccurrences(of: "#", with: "%23") | ||
let path = [user, repo, ref, escapingPath].joined(separator: "/") | ||
if let name = escapingPath.components(separatedBy: "/").last { | ||
print("Downloading: " + name) | ||
} | ||
try await fetchFileWith(path: path, directory: escapingPath) | ||
} | ||
} | ||
} | ||
try await group.waitForAll() | ||
} | ||
} | ||
|
||
/// Fetches the list of files from the GitHub repository using the GitHub Trees API. | ||
private func gitTreesApi(user: String, repository: String, directory: String) async throws -> [Any] { | ||
|
||
let directory = directory.components(separatedBy: "/").last ?? "" | ||
var requestUrlString = GitHubResource.gitApi.absoluteString | ||
requestUrlString.append("/\(user)/\(repository)/git/trees/HEAD?recursive=1") | ||
guard let requestUrl = URL(string: requestUrlString) else { return [] } | ||
|
||
let (data, _) = try await URLSession.shared.data(from: requestUrl) | ||
|
||
let contents = try JSONSerialization.jsonObject(with: data) as? [String: Any] | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
guard let tree = contents?["tree"] as? [Any] else { return [] } | ||
var filePaths = [String]() | ||
for item in tree { | ||
guard let item = item as? [String: Any] else { continue } | ||
let type = (item["type"] as? String) ?? "" | ||
let path = (item["path"] as? String) ?? "" | ||
let serviceName = (path.components(separatedBy: "/") | ||
.last?.components(separatedBy: ".").first) ?? "" | ||
if !expectedServices.isEmpty , !expectedServices.contains(serviceName) { | ||
continue | ||
} | ||
// Check for subdirectory | ||
let currentDirectory = path.components(separatedBy: "/").dropLast().last ?? "" | ||
if (type == "blob" && currentDirectory.elementsEqual(directory)){ | ||
filePaths.append(path); | ||
} | ||
} | ||
return filePaths | ||
} | ||
|
||
/// Extracts user, repository, directory, and reference from the GitHub repository URL. | ||
private func extractGitComponents(from url: String) throws -> (user: String, repo: String, directory: String, ref: String)? { | ||
let pattern = #"github\.com\/([^\/]+)\/([^\/]+)\/(?:tree|blob)\/([^\/]+)\/(.*)"# | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let regex = try NSRegularExpression(pattern: pattern, options: []) | ||
|
||
guard let match = regex.firstMatch(in: url, options: [], range: NSRange(location: 0, length: url.utf16.count)) else { | ||
return nil | ||
} | ||
|
||
let nsUrl = url as NSString | ||
let userRange = match.range(at: 1) | ||
let repoRange = match.range(at: 2) | ||
let refRange = match.range(at: 3) | ||
let directoryRange = match.range(at: 4) | ||
|
||
let user = nsUrl.substring(with: userRange) | ||
let repo = nsUrl.substring(with: repoRange) | ||
let ref = nsUrl.substring(with: refRange) | ||
let directory = nsUrl.substring(with: directoryRange) | ||
|
||
return (user, repo, directory, ref) | ||
} | ||
|
||
/// Downloads raw file from the GitHub repository. | ||
private func fetchFileWith(path: String, directory: String) async throws { | ||
guard let downloadURL = URL(string: "https://raw.githubusercontent.com/" + path) else { return } | ||
|
||
let (data, _) = try await URLSession.shared.data(from: downloadURL) | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let fileName = directory.components(separatedBy: "/").last ?? directory | ||
let filePath = outputFolder + "/\(fileName)" | ||
|
||
FileManager.default.createFile(atPath: filePath, contents: data) | ||
} | ||
} | ||
|
||
// MARK: - Model and Endpoint URLs - | ||
|
||
enum Repo { | ||
/// Model files github directory. | ||
static let modelDirectory = "https://github.com/soto-project/soto/tree/c36f311add37d4868b6b1688d88d320a5626d6ef/models" | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// Endpoints github directory. | ||
static let endpointsDirectory = "https://github.com/soto-project/soto/tree/c36f311add37d4868b6b1688d88d320a5626d6ef/models/endpoints" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Soto for AWS open source project | ||
// | ||
// Copyright (c) 2017-2024 the Soto project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of Soto project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
import PackagePlugin | ||
import Foundation | ||
|
||
@main | ||
struct SotoCodeModelDownloader: CommandPlugin { | ||
|
||
func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { | ||
|
||
// get config File | ||
let configfile = context.package.targets.compactMap({ $0 as? SourceModuleTarget }) | ||
.compactMap({ $0.sourceFiles | ||
.first(where: { $0.path.lastComponent | ||
.contains("soto.config.json") })?.path }).first | ||
|
||
guard let configfile else { | ||
Diagnostics.error("can not find the soto.config.json file in the target") | ||
return | ||
} | ||
|
||
// extracting services from config | ||
let services = try getSerivesFrom(path: configfile.string) | ||
|
||
let mainDirectory = configfile.removingLastComponent() | ||
let outputFolder = mainDirectory.appending("aws").string | ||
|
||
// Download Model files | ||
let modelDownloader = GitHubResource(inputFolder: Repo.modelDirectory, | ||
outputFolder: outputFolder + "/models", | ||
expectedServices: services) | ||
// Download Endpoint File | ||
let endpointDownloader = GitHubResource(inputFolder: Repo.endpointsDirectory, | ||
outputFolder: outputFolder) | ||
|
||
try await withThrowingTaskGroup(of: Void.self) { group in | ||
for resource in [endpointDownloader, modelDownloader] { | ||
group.addTask { | ||
try await resource.download() | ||
} | ||
} | ||
try await group.waitForAll() | ||
} | ||
print("Downloaded resources in : \(outputFolder)") | ||
} | ||
|
||
private func getSerivesFrom(path: String) throws -> [String] { | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let data = try Data(contentsOf: URL(filePath: path)) | ||
guard let json = try JSONSerialization.jsonObject(with: data) as? [String : Any] else { return [] } | ||
Arpit160399 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if let services = json["services"] as? [String : Any] { | ||
let servicesName = services.keys.map({ $0 }) | ||
return servicesName | ||
} | ||
return [] | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Soto (as with SwiftNIO) we look to support the last three versions of Swift. So for the moment that'd be 5.8, 5.9 and 5.10. Currently with your setup the download plugin is only available for 5.9, not 5.10. I would instead create a
Package.@swift-5.8.swift
which is a copy of the currentPackage.swift
and make this version the defaultPackage.swift
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, with the current package setup, if the current Swift version doesn't match any version-specific manifest, the package manager will pick the manifest with the most compatible tools version. This means the package manager will pick
Package.swift
for Swift 5.8 andPackage@swift-5.9.swift
for Swift 5.9 and above, as its tools version will be most compatible with future versions of the package manager. I read this in the documentation here.In terms of readability and maintainability, I agree with your approach of keeping the 5.8 version tag instead of 5.9.