Skip to content

Commit

Permalink
feat: support from/to paths in file patterns for extraFiles and extra…
Browse files Browse the repository at this point in the history
…Resources

Closes #650
  • Loading branch information
ha-D authored and develar committed Aug 26, 2016
1 parent 2a24a3f commit 308438f
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 60 deletions.
17 changes: 17 additions & 0 deletions docs/Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,5 +225,22 @@ May be specified in the platform options (e.g. in the `build.mac`).
Remember that `!doNotCopyMe/**/*` would match the files *in* the `doNotCopyMe` directory, but not the directory itself, so the [empty directory](https://github.com/gulpjs/gulp/issues/165#issuecomment-32613179) would be created.
Solution — use macro `${/*}`, e.g. `!doNotCopyMe${/*}`.
## Source and Destination Directories
You may also specify custom source and destination directories by using JSON objects instead of simple glob patterns.
Note this only works for `extraFiles` and `extraResources`.
```js
[
{
"from": "path/to/source",
"to": "path/to/destination",
"filter": ["**/*", "!foo/*.js"]
}
]
```
If `from` is given as a relative path, it is relative to the project directory.
If `to` is given as a relative path, it is relative to the app's content directory for `extraFiles` and the app's resource directory for `extraResources`.
You can you `${os}` and `${arch}` in the `from` and `to` fields as well.
# Build Version Management
`CFBundleVersion` (MacOS) and `FileVersion` (Windows) will be set automatically to `version`.`build_number` on CI server (Travis, AppVeyor and CircleCI supported).
213 changes: 153 additions & 60 deletions src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,85 @@ export abstract class TargetEx extends Target {
abstract async build(appOutDir: string, arch: Arch): Promise<any>
}

export interface FilePattern {
from?: string
to?: string
filter?: Array<string> | string
}

export interface FileMatchOptions {
arch: string,
os: string
}

export class FileMatcher {
readonly from: string
readonly to: string
readonly options: FileMatchOptions

readonly patterns: Array<string>

constructor(from: string, to: string, options: FileMatchOptions, patterns?: Array<string> | string | n) {
this.options = options
this.patterns = []

this.from = this.expandPattern(from)
this.to = this.expandPattern(to)

if (patterns != null && !Array.isArray(patterns)) {
this.patterns = [patterns]
}
else if (patterns != null) {
this.patterns = patterns
}
}

addPattern(pattern: string) {
this.patterns.push(pattern)
}

isEmpty() {
return this.patterns.length === 0
}

getParsedPatterns(fromDir?: string): Array<Minimatch> {
const minimatchOptions = {}

const parsedPatterns: Array<Minimatch> = []
const pathDifference = fromDir ? path.relative(fromDir, this.from) : null

for (let i = 0; i < this.patterns.length; i++) {
let expandedPattern = this.expandPattern(this.patterns[i])

if (pathDifference) {
expandedPattern = path.join(pathDifference, expandedPattern)
}

const parsedPattern = new Minimatch(expandedPattern, minimatchOptions)
parsedPatterns.push(parsedPattern)

if (!hasMagic(parsedPattern)) {
// https://github.com/electron-userland/electron-builder/issues/545
// add **/*
parsedPatterns.push(new Minimatch(`${expandedPattern}/*/**`, minimatchOptions))
}
}

return parsedPatterns
}

createFilter(ignoreFiles?: Set<string>, rawFilter?: (file: string) => boolean, excludePatterns?: Array<Minimatch> | n): (file: string) => boolean {
return createFilter(this.from, this.getParsedPatterns(), ignoreFiles, rawFilter, excludePatterns)
}

private expandPattern(pattern: string): string {
return pattern
.replace(/\$\{arch}/g, this.options.arch)
.replace(/\$\{os}/g, this.options.os)
.replace(/\$\{\/\*}/g, "{,/**/*,/**/.*}")
}
}

export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions> {
readonly options: PackagerOptions

Expand Down Expand Up @@ -156,16 +235,22 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>

abstract pack(outDir: string, arch: Arch, targets: Array<Target>, postAsyncTasks: Array<Promise<any>>): Promise<any>

private getExtraFilePatterns(isResources: boolean, arch: Arch, customBuildOptions: DC): Array<Minimatch> | null {
const patterns = this.getFilePatterns(isResources ? "extraResources" : "extraFiles", customBuildOptions)
return patterns == null || patterns.length === 0 ? null : this.getParsedPatterns(patterns, arch)
private getExtraFilePatterns(isResources: boolean, appOutDir: string, fileMatchOptions: FileMatchOptions, customBuildOptions: DC): Array<FileMatcher> | n {
const base = isResources ? this.getResourcesDir(appOutDir) : (this.platform === Platform.MAC ? path.join(appOutDir, `${this.appInfo.productFilename}.app`, "Contents") : appOutDir)
return this.getFilePatterns(isResources ? "extraResources" : "extraFiles", this.projectDir, base, true, fileMatchOptions, customBuildOptions)
}

protected async doPack(options: ElectronPackagerOptions, outDir: string, appOutDir: string, platformName: string, arch: Arch, platformSpecificBuildOptions: DC) {
const asarOptions = this.computeAsarOptions(platformSpecificBuildOptions)
const fileMatchOptions: FileMatchOptions = {
arch: Arch[arch],
os: this.platform.buildConfigurationKey
}

const extraResourcePatterns = this.getExtraFilePatterns(true, appOutDir, fileMatchOptions, platformSpecificBuildOptions)
const extraFilePatterns = this.getExtraFilePatterns(false, appOutDir, fileMatchOptions, platformSpecificBuildOptions)

const extraResourcePatterns = this.getExtraFilePatterns(true, arch, platformSpecificBuildOptions)
const extraFilePatterns = this.getExtraFilePatterns(false, arch, platformSpecificBuildOptions)
const resourcesPath = this.platform === Platform.MAC ? path.join(appOutDir, "Electron.app", "Contents", "Resources") : path.join(appOutDir, "resources")

const p = pack(options, appOutDir, platformName, Arch[arch], this.info.electronVersion, async() => {
const ignoreFiles = new Set([path.resolve(this.info.appDir, outDir), path.resolve(this.info.appDir, this.buildResourcesDir)])
Expand All @@ -176,11 +261,13 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
}
}

let patterns = this.getFilePatterns("files", platformSpecificBuildOptions)
if (patterns == null || patterns.length === 0) {
patterns = ["**/*"]
let patterns = this.getFilePatterns("files", this.info.appDir, path.join(resourcesPath, "app"), false, fileMatchOptions, platformSpecificBuildOptions)
let defaultMatcher = patterns != null ? patterns[0] : new FileMatcher(this.info.appDir, path.join(resourcesPath, "app"), fileMatchOptions)

if (defaultMatcher.isEmpty()) {
defaultMatcher.addPattern("**/*")
}
patterns.push("!**/node_modules/*/{README.md,README,readme.md,readme,test}")
defaultMatcher.addPattern("!**/node_modules/*/{README.md,README,readme.md,readme,test}")

let rawFilter: any = null
const deprecatedIgnore = (<any>this.devMetadata.build).ignore
Expand All @@ -194,24 +281,21 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
rawFilter = deprecatedUserIgnoreFilter(options, this.info.appDir)
}

const filePatterns = this.getParsedPatterns(patterns, arch)
let excludePatterns: Array<Minimatch> | null = null
if (!this.info.isTwoPackageJsonProjectLayoutUsed) {
if (extraResourcePatterns != null) {
excludePatterns = extraResourcePatterns
let excludePatterns: Array<Minimatch> = []
if (extraResourcePatterns != null) {
for (let i = 0; i < extraResourcePatterns.length; i++) {
const patterns = extraResourcePatterns[i].getParsedPatterns(this.info.projectDir)
excludePatterns = excludePatterns.concat(patterns)
}
if (extraFilePatterns != null) {
if (excludePatterns == null) {
excludePatterns = extraFilePatterns
}
else {
excludePatterns = excludePatterns.concat(extraFilePatterns)
}
}
if (extraFilePatterns != null) {
for (let i = 0; i < extraFilePatterns.length; i++) {
const patterns = extraFilePatterns[i].getParsedPatterns(this.info.projectDir)
excludePatterns = excludePatterns.concat(patterns)
}
}

const resourcesPath = this.platform === Platform.MAC ? path.join(appOutDir, "Electron.app", "Contents", "Resources") : path.join(appOutDir, "resources")
const filter = createFilter(this.info.appDir, filePatterns, ignoreFiles, rawFilter, excludePatterns)
const filter = defaultMatcher.createFilter(ignoreFiles, rawFilter, excludePatterns.length ? excludePatterns : null)
const promise = asarOptions == null ?
copyFiltered(this.info.appDir, path.join(resourcesPath, "app"), filter, this.platform === Platform.WINDOWS)
: createAsarArchive(this.info.appDir, resourcesPath, asarOptions, filter)
Expand All @@ -227,8 +311,8 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
})
await task(`Packaging for platform ${platformName} ${Arch[arch]} using electron ${this.info.electronVersion} to ${path.relative(this.projectDir, appOutDir)}`, p)

await this.doCopyExtraFiles(true, appOutDir, extraResourcePatterns)
await this.doCopyExtraFiles(false, appOutDir, extraFilePatterns)
await this.doCopyExtraFiles(extraResourcePatterns)
await this.doCopyExtraFiles(extraFilePatterns)

const afterPack = this.devMetadata.build.afterPack
if (afterPack != null) {
Expand Down Expand Up @@ -294,54 +378,63 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
})
}

private expandPattern(pattern: string, arch: Arch): string {
return pattern
.replace(/\$\{arch}/g, Arch[arch])
.replace(/\$\{os}/g, this.platform.buildConfigurationKey)
.replace(/\$\{\/\*}/g, "{,/**/*,/**/.*}")
}

private doCopyExtraFiles(isResources: boolean, appOutDir: string, patterns: Array<Minimatch> | null): Promise<any> {
const base = isResources ? this.getResourcesDir(appOutDir) : (this.platform === Platform.MAC ? path.join(appOutDir, `${this.appInfo.productFilename}.app`, "Contents") : appOutDir)
private doCopyExtraFiles(patterns: Array<FileMatcher> | n): Promise<any> {
if (patterns == null || patterns.length === 0) {
return BluebirdPromise.resolve()
}
else {
return copyFiltered(this.projectDir, base, createFilter(this.projectDir, patterns), this.platform === Platform.WINDOWS)
const promises: Array<Promise<any>> = []
for (let i = 0; i < patterns.length; i++) {
if (patterns[i].isEmpty()) {
patterns[i].addPattern("**/*")
}
promises.push(copyFiltered(patterns[i].from, patterns[i].to, patterns[i].createFilter(), this.platform === Platform.WINDOWS))
}
return BluebirdPromise.all(promises)
}
}

private getParsedPatterns(patterns: Array<string>, arch: Arch): Array<Minimatch> {
const minimatchOptions = {}
const parsedPatterns: Array<Minimatch> = []
for (let i = 0; i < patterns.length; i++) {
const pattern = this.expandPattern(patterns[i], arch)
const parsedPattern = new Minimatch(pattern, minimatchOptions)
parsedPatterns.push(parsedPattern)
if (!hasMagic(parsedPattern)) {
// https://github.com/electron-userland/electron-builder/issues/545
// add **/*
parsedPatterns.push(new Minimatch(`${pattern}/*/**`, minimatchOptions))
private getFilePatterns(name: "files" | "extraFiles" | "extraResources", defaultSrc: string, defaultDest: string, allowAdvancedMatching: boolean, fileMatchOptions: FileMatchOptions, customBuildOptions: DC): Array<FileMatcher> | n {
let globalPatterns: Array<string | FilePattern> | string | n = (<any>this.devMetadata.build)[name]
let platformSpecificPatterns: Array<string | FilePattern> | string | n = (<any>customBuildOptions)[name]

const defaultMatcher = new FileMatcher(defaultSrc, defaultDest, fileMatchOptions)
const fileMatchers: Array<FileMatcher> = []

function addPatterns(patterns: Array<string | FilePattern> | string | n) {
if (patterns == null) {
return
}
else if (!Array.isArray(patterns)) {
defaultMatcher.addPattern(patterns)
return
}

for (let i = 0; i < patterns.length; i++) {
const pattern = patterns[i]
if (typeof pattern === "string") {
defaultMatcher.addPattern(pattern)
}
else if (allowAdvancedMatching) {
const from = pattern.from ? (path.isAbsolute(pattern.from) ? pattern.from : path.join(defaultSrc, pattern.from)) : defaultSrc
const to = pattern.to ? (path.isAbsolute(pattern.to) ? pattern.to : path.join(defaultDest, pattern.to)) : defaultDest
fileMatchers.push(new FileMatcher(from, to, fileMatchOptions, pattern.filter))
}
else {
throw new Error(`Advanced file copying not supported for "${name}"`)
}
}
}

return parsedPatterns
}
addPatterns(globalPatterns)
addPatterns(platformSpecificPatterns)

private getFilePatterns(name: "files" | "extraFiles" | "extraResources", customBuildOptions: DC): Array<string> | n {
let patterns: Array<string> | string | n = (<any>this.devMetadata.build)[name]
if (patterns != null && !Array.isArray(patterns)) {
patterns = [patterns]
if (!defaultMatcher.isEmpty()) {
// Default matcher should be first in the array
fileMatchers.unshift(defaultMatcher)
}

let platformSpecificPatterns: Array<string> | string | n = (<any>customBuildOptions)[name]
if (platformSpecificPatterns != null) {
if (!Array.isArray(platformSpecificPatterns)) {
platformSpecificPatterns = [platformSpecificPatterns]
}
return patterns == null ? platformSpecificPatterns : Array.from(new Set(patterns.concat(platformSpecificPatterns)))
}
return patterns
return fileMatchers.length ? fileMatchers : null
}

private getResourcesDir(appOutDir: string): string {
Expand Down

0 comments on commit 308438f

Please sign in to comment.