Skip to content
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
1 change: 1 addition & 0 deletions scripts/verify-loop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ test -e "$WORK/expanded-empty/Payload"
RECEIPT="$WORK/ReceiptOnly"
run "$BIN" --create "$RECEIPT"
rm -rf "$RECEIPT/payload" "$RECEIPT/scripts"
run "$BIN" --lint "$RECEIPT"
run "$BIN" "$RECEIPT"
run /usr/sbin/pkgutil --expand "$RECEIPT/build/ReceiptOnly-1.0.pkg" "$WORK/expanded-receipt"
test ! -e "$WORK/expanded-receipt/Payload"
Expand Down
7 changes: 2 additions & 5 deletions swiftpkg/Linter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,11 @@ public struct Linter {
findings.append(LintFinding(.warning, "notarization is configured but signing is not; notarization requires a Developer ID signature"))
}

let payload = project.appendingPathComponent("payload", isDirectory: true)
// A project with neither payload nor scripts is a valid receipt-only
// package; PackageBuilder emits `pkgbuild --nopayload` for that case.
let scripts = project.appendingPathComponent("scripts", isDirectory: true)
let hasPayload = fileManager.directoryExists(at: payload)
let hasScripts = fileManager.directoryExists(at: scripts)
&& ((try? fileManager.contents(at: scripts).contains { $0 != ".DS_Store" }) ?? false)
if !hasPayload, !hasScripts {
findings.append(LintFinding(.error, "project has neither a payload directory nor a non-empty scripts directory"))
}

if hasScripts {
findings.append(contentsOf: lintScripts(in: scripts))
Expand Down
12 changes: 6 additions & 6 deletions swiftpkgTests/LinterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ struct LinterTests {
#expect(findings.contains { $0.message.contains("notarization is configured but signing") })
}

@Test("errors when there is neither payload nor scripts")
func errorsOnEmptyProject() throws {
@Test("a receipt-only project with no payload or scripts lints cleanly")
func receiptOnlyProjectIsClean() throws {
let (temp, project) = try makeProject(buildInfo: #"{"name":"App-1.0.pkg","identifier":"com.example.app","version":"1.0"}"#, addPayload: false)
defer { temp.remove() }
let findings = try Linter().lint(project: project, requestedFormat: nil)
#expect(findings.contains { $0.severity == .error && $0.message.contains("neither a payload") })
#expect(findings.isEmpty)
}

/// Payload-free, since a scripts-only project is supported: the script
Expand All @@ -121,14 +121,14 @@ struct LinterTests {
#expect(findings.allSatisfy { $0.severity == .warning })
}

@Test("a scripts directory holding only .DS_Store does not count as scripts")
func emptyScriptsDirectoryStillErrors() throws {
@Test("a scripts directory holding only .DS_Store is still a valid receipt-only project")
func emptyScriptsDirectoryIsReceiptOnly() throws {
let (temp, project) = try makeProject(buildInfo: #"{"name":"App-1.0.pkg","identifier":"com.example.app","version":"1.0"}"#, addPayload: false)
defer { temp.remove() }
let scripts = project.appendingPathComponent("scripts", isDirectory: true)
try FileManager.default.createDirectory(at: scripts, withIntermediateDirectories: false)
try write("", to: scripts.appendingPathComponent(".DS_Store"))
let findings = try Linter().lint(project: project, requestedFormat: nil)
#expect(findings.contains { $0.severity == .error && $0.message.contains("neither a payload") })
#expect(findings.isEmpty)
}
}
8 changes: 6 additions & 2 deletions swiftpkgTests/ReceiptOnlyBuildTests.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import Foundation
import Testing
@testable import SwiftPkgCore
@testable import swiftpkg

struct ReceiptOnlyBuildTests {

// A project with neither payload nor scripts is valid: it builds a
// receipt-only package. pkgbuild must be invoked with --nopayload (and no
// --root), matching munki-pkg — otherwise the build would fail looking for
// a payload that isn't there.
@Test("receipt-only project builds with pkgbuild --nopayload")
func receiptOnlyUsesNopayload() async throws {
@Test("receipt-only project lints cleanly and builds with pkgbuild --nopayload")
func receiptOnlyLintsAndBuildsWithNopayload() async throws {
let temp = try TemporaryDirectory()
defer { temp.remove() }
let project = temp.url.appendingPathComponent("ReceiptOnly", isDirectory: true)
Expand All @@ -19,6 +20,9 @@ struct ReceiptOnlyBuildTests {
to: project.appendingPathComponent("build-info.json")
)
let runner = RecordingRunner()
let lintCode = await SwiftPkg.run(arguments: ["--lint", project.path], runner: runner)
#expect(lintCode == 0)

runner.onRun = { executable, arguments in
guard executable.hasSuffix("pkgbuild"), let output = arguments.last else { return }
try write("fake package", to: URL(fileURLWithPath: output))
Expand Down