Skip to content
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

Third party licenses #1030

Merged
merged 3 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions Scripts/GenerateLicenses.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/xcrun --sdk macosx swift

// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
//
// stringlint:disable

import Foundation

// Get the Derived Data path and the project's name
let derivedDataPath = getDerivedDataPath() ?? ""
let projectName = ProcessInfo.processInfo.environment["PROJECT_NAME"] ?? ""
let projectPath = ProcessInfo.processInfo.environment["PROJECT_DIR"] ?? FileManager.default.currentDirectoryPath

let packageResolutionFilePath = "\(projectPath)/\(projectName).xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved"
let packageCheckoutsPath = "\(derivedDataPath)/SourcePackages/checkouts/"
let packageArtifactsPath = "\(derivedDataPath)/SourcePackages/artifacts/"

func getDerivedDataPath() -> String? {
// Define the regular expression pattern to extract the DerivedData path
let regexPattern = ".*DerivedData/[^/]*"
guard
let buildDir = ProcessInfo.processInfo.environment["BUILD_DIR"],
let regex = try? NSRegularExpression(pattern: regexPattern)
else { return nil }

let range = NSRange(location: 0, length: buildDir.utf16.count)

// Perform the regex matching
if let match = regex.firstMatch(in: buildDir, options: [], range: range) {
// Extract the matching portion (the DerivedData path)
if let range = Range(match.range, in: buildDir) {
return String(buildDir[range])
}
} else {
print("No DerivedData path found in BUILD_DIR")
}

return nil
}

// Function to list all directories (Swift package checkouts) inside the SourcePackages/checkouts directory
func listDirectories(atPath path: String) -> [String] {
let fileManager = FileManager.default
do {
let items = try fileManager.contentsOfDirectory(atPath: path)
return items.filter { item in
var isDir: ObjCBool = false
let fullPath = path + "/" + item
return fileManager.fileExists(atPath: fullPath, isDirectory: &isDir) && isDir.boolValue
}
} catch {
print("Error reading contents of directory: \(error)")
return []
}
}

// Function to find and read LICENSE files in each package
func findLicenses(in packagesPath: String) -> [(package: String, licenseContent: String)] {
var licenses: [(package: String, licenseContent: String)] = []
let packages: [String] = listDirectories(atPath: packagesPath)

print("\(packages.count) packages found in \(packagesPath)")

packages.forEach { package in
let packagePath = "\(packagesPath)/\(package)"
scanDirectory(atPath: packagePath) { filePath in
if filePath.lowercased().contains("license") || filePath.lowercased().contains("copying") {
if let licenseContent = try? String(contentsOfFile: filePath, encoding: .utf8) {
licenses.append((package, licenseContent))
}
}
}
}

return licenses
}

func findPackageDependencyNames(in resolutionFilePath: String) throws -> Set<String> {
struct ResolvedPackages: Codable {
struct Pin: Codable {
struct State: Codable {
let revision: String
let version: String
}

let identity: String
let kind: String
let location: String
let state: State
}

let originHash: String
let pins: [Pin]
let version: Int
}

do {
let data: Data = try Data(contentsOf: URL(fileURLWithPath: resolutionFilePath))
let resolvedPackages: ResolvedPackages = try JSONDecoder().decode(ResolvedPackages.self, from: data)

print("Found \(resolvedPackages.pins.count) resolved packages.")
return Set(resolvedPackages.pins.map { $0.identity.lowercased() })
}
catch {
print("error: Failed to load list of resolved packages")
throw error
}
}

func scanDirectory(atPath path: String, foundFile: (String) -> Void) {
if let enumerator = FileManager.default.enumerator(atPath: path) {
for case let file as String in enumerator {
let fullPath = "\(path)/\(file)"
if FileManager.default.fileExists(atPath: fullPath, isDirectory: nil) {
foundFile(fullPath)
}
}
}
}

// Write licenses to a plist file
func writePlist(licenses: [(package: String, licenseContent: String)], resolvedPackageNames: Set<String>, outputPath: String) {
var plistArray: [[String: String]] = []
let finalLicenses: [(package: String, licenseContent: String)] = licenses
.filter { resolvedPackageNames.contains($0.package.lowercased()) }
.sorted(by: { $0.package.lowercased() < $1.package.lowercased() })

print("\(finalLicenses.count) being written to plist.")

finalLicenses.forEach { license in
plistArray.append([
"Title": license.package,
"License": license.licenseContent
])
}

let plistData = try! PropertyListSerialization.data(fromPropertyList: plistArray, format: .xml, options: 0)
let plistURL = URL(fileURLWithPath: outputPath)
try? plistData.write(to: plistURL)
}

// Execute the license discovery process
let licenses = findLicenses(in: packageCheckoutsPath) + findLicenses(in: packageArtifactsPath)
let resolvedPackageNames = try findPackageDependencyNames(in: packageResolutionFilePath)

// Specify the path for the output plist
let outputPlistPath = "\(projectPath)/\(projectName)/Meta/Settings.bundle/ThirdPartyLicenses.plist"
writePlist(licenses: licenses, resolvedPackageNames: resolvedPackageNames, outputPath: outputPlistPath)

print("Licenses generated successfully at \(outputPlistPath)")
23 changes: 23 additions & 0 deletions Session.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,7 @@
94367C422C6C828500814252 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
943C6D812B75E061004ACE64 /* Message+DisappearingMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+DisappearingMessages.swift"; sourceTree = "<group>"; };
943C6D832B86B5F1004ACE64 /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = "<group>"; };
9471CAA72CACFB4E00090FB7 /* GenerateLicenses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateLicenses.swift; sourceTree = "<group>"; };
9473386D2BDF5F3E00B9E169 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = "<group>"; };
947AD68F2C8968FF000B2730 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
94B3DC162AF8592200C88531 /* QuoteView_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteView_SwiftUI.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4387,6 +4388,7 @@
FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */,
FD5CE3442A3C5D96001A6DE3 /* DecryptExportedKey.swift */,
FDEF57642C44B8C200131302 /* ProcessIP2CountryData.swift */,
9471CAA72CACFB4E00090FB7 /* GenerateLicenses.swift */,
);
path = Scripts;
sourceTree = "<group>";
Expand Down Expand Up @@ -4777,6 +4779,7 @@
FD5E93D32C12D3990038C25A /* Add App Group To Build Info Plist */,
FDC498BF2AC1747900EDD897 /* Ensure Localizable.strings included */,
FD0B1FA92CA3805C00F60F46 /* Ensure InfoPlist.xcstrings updated */,
9471CAA62CACFB0600090FB7 /* Generate Licenses Plist */,
);
buildRules = (
);
Expand Down Expand Up @@ -5283,6 +5286,26 @@
shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\" update\n";
showEnvVarsInLog = 0;
};
9471CAA62CACFB0600090FB7 /* Generate Licenses Plist */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Generate Licenses Plist";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Scripts/GenerateLicenses.swift\"\n";
showEnvVarsInLog = 0;
};
FD5E93D32C12D3990038C25A /* Add App Group To Build Info Plist */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
Expand Down
Loading