mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
Support for upx and more go:embed.
This commit is contained in:
parent
46ea3e6074
commit
08f4476087
14 changed files with 172 additions and 103 deletions
54
v2/cmd/wails/internal/commands/build/README.md
Normal file
54
v2/cmd/wails/internal/commands/build/README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Build
|
||||
|
||||
The build command processes the Wails project and generates an application binary.
|
||||
|
||||
## Usage
|
||||
|
||||
`wails build <flags>`
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Details | Default |
|
||||
| :------------- | :----------- | :------ |
|
||||
| -clean | Clean the bin directory before building | |
|
||||
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
|
||||
| -ldflags "custom ld flags" | Use given ldflags | |
|
||||
| -o path/to/binary | Compile to given path/filename | |
|
||||
| -k | Keep generated assets | |
|
||||
| -package | Create a platform specific package | |
|
||||
| -production | Compile in production mode: -ldflags="-w -s" + "-h windows" on Windows | |
|
||||
| -tags | Build tags to pass to Go compiler (quoted and space separated) | |
|
||||
| -upx | Compress final binary with UPX (if installed) | |
|
||||
| -upxflags "custom flags" | Flags to pass to upx | |
|
||||
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
|
||||
|
||||
## The Build Process
|
||||
|
||||
The build process is as follows:
|
||||
|
||||
- The flags are processed, and an Options struct built containing the build context.
|
||||
- The type of target is determined, and a custom build process is followed for target.
|
||||
|
||||
### Desktop Target
|
||||
|
||||
- The frontend dependencies are installed. The command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
|
||||
- The frontend is then built. This command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
|
||||
- The project directory is checked to see if the `build` directory exists. If not, it is created and default project assets are copied to it.
|
||||
- An asset bundle is then created by reading the `html` key from `wails.json` and loading the referenced file. This is then parsed, looking for local Javascript and CSS references. Those files are in turn loaded into memory, converted to C data and saved into the asset bundle located at `build/assets.h`, which also includes the original HTML.
|
||||
- The application icon is then processed: if there is no `build/appicon.png`, a default icon is copied. On Windows, an `app.ico` file is generated from this png. On Mac, `icons.icns` is generated.
|
||||
- If there are icons in the `build/tray` directory, these are processed, converted to C data and saved as `build/trayicons.h`, ready for the compilation step.
|
||||
- If there are icons in the `build/dialog` directory, these are processed, converted to C data and saved as `build/userdialogicons.h`, ready for the compilation step.
|
||||
- If the `-package` flag is given for a Windows target, the Windows assets in the `build/windows` directory are processed: manifest + icons compiled to a `.syso` file (deleted after compilation).
|
||||
- If we are building a universal binary for Mac, the application is compiled for both `arm64` and `amd64`. The `lipo` tool is then executed to create the universal binary.
|
||||
- If we are not building a universal binary for Mac, the application is built using `go build`, using build tags to indicate type of application and build mode (debug/production).
|
||||
- If the `-upx` flag was provided, `upx` is invoked to compress the binary. Custom flags may be provided using the `-upxflags` flag.
|
||||
- If the `package` flag is given for a non Windows target, the application is bundled for the platform. On Mac, this creates a `.app` with the processed icons, the `Info.plist` in `build/darwin` and the compiled binary.
|
||||
|
||||
### Server Target
|
||||
|
||||
TBD
|
||||
|
||||
### Hybrid Target
|
||||
|
||||
TBD
|
||||
|
||||
|
|
@ -36,7 +36,10 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
|
||||
|
||||
compress := false
|
||||
command.BoolFlag("compress", "Compress final binary", &compress)
|
||||
command.BoolFlag("upx", "Compress final binary with UPX (if installed)", &compress)
|
||||
|
||||
compressFlags := ""
|
||||
command.StringFlag("upxflags", "Flags to pass to upx", &compressFlags)
|
||||
|
||||
// Setup Platform flag
|
||||
platform := runtime.GOOS
|
||||
|
|
@ -131,6 +134,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||
KeepAssets: keepAssets,
|
||||
Verbosity: verbosity,
|
||||
Compress: compress,
|
||||
CompressFlags: compressFlags,
|
||||
UserTags: userTags,
|
||||
}
|
||||
|
||||
|
|
|
|||
22
v2/cmd/wails/internal/commands/dev/README.md
Normal file
22
v2/cmd/wails/internal/commands/dev/README.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Dev
|
||||
|
||||
The dev command allows you to develop your application through a standard browser.
|
||||
|
||||
## Usage
|
||||
|
||||
`wails dev <flags>`
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Details | Default |
|
||||
| :------------- | :----------- | :------ |
|
||||
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
|
||||
| -ldflags "custom ld flags" | Use given ldflags | |
|
||||
| -e list,of,extensions | File extensions to trigger rebuilds | go |
|
||||
| -w | Show warnings | false |
|
||||
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
|
||||
| -loglevel | Loglevel to pass to the application - Trace, Debug, Info, Warning, Error | Debug |
|
||||
|
||||
## How it works
|
||||
|
||||
The project is build
|
||||
|
|
@ -58,6 +58,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
|||
showWarnings := false
|
||||
command.BoolFlag("w", "Show warnings", &showWarnings)
|
||||
|
||||
// Verbosity
|
||||
verbosity := 1
|
||||
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
|
||||
|
||||
loglevel := ""
|
||||
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
|
||||
|
||||
|
|
@ -90,13 +94,13 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
|||
debounceQuit := make(chan bool, 1)
|
||||
|
||||
var passthruArgs []string
|
||||
if len(os.Args) > 2 {
|
||||
passthruArgs = os.Args[2:]
|
||||
}
|
||||
//if len(os.Args) > 2 {
|
||||
// passthruArgs = os.Args[2:]
|
||||
//}
|
||||
|
||||
// Do initial build
|
||||
logger.Println("Building application for development...")
|
||||
newProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs)
|
||||
newProcess, appBinary, err := restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
|
||||
if newProcess != nil {
|
||||
debugBinaryProcess = newProcess
|
||||
}
|
||||
|
|
@ -104,6 +108,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var newBinaryProcess *process.Process
|
||||
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
|
||||
// logger.Println("event: %+v", event)
|
||||
|
||||
|
|
@ -147,7 +152,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
|||
// Do a rebuild
|
||||
|
||||
// Try and build the app
|
||||
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs)
|
||||
newBinaryProcess, _, err = restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
|
||||
if err != nil {
|
||||
fmt.Printf("Error during build: %s", err.Error())
|
||||
return
|
||||
|
|
@ -209,6 +214,12 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Remove dev binary
|
||||
err = os.Remove(appBinary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
LogGreen("Development mode exited")
|
||||
|
||||
return nil
|
||||
|
|
@ -236,14 +247,14 @@ exit:
|
|||
}
|
||||
}
|
||||
|
||||
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string, passthruArgs []string) (*process.Process, error) {
|
||||
func restartApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string, passthruArgs []string, verbosity int) (*process.Process, string, error) {
|
||||
|
||||
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
|
||||
appBinary, err := buildApp(logger, ldflags, compilerCommand, verbosity)
|
||||
println()
|
||||
if err != nil {
|
||||
LogRed("Build error - continuing to run current version")
|
||||
LogDarkYellow(err.Error())
|
||||
return nil, nil
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
// Kill existing binary if need be
|
||||
|
|
@ -275,18 +286,21 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
|
|||
logger.Fatal("Unable to start application: %s", err.Error())
|
||||
}
|
||||
|
||||
return newProcess, nil
|
||||
return newProcess, appBinary, nil
|
||||
}
|
||||
|
||||
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
|
||||
func buildApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, verbosity int) (string, error) {
|
||||
|
||||
// Create random output file
|
||||
outputFile := fmt.Sprintf("dev-%d", time.Now().Unix())
|
||||
outputFile := "wailsdev"
|
||||
if runtime.GOOS == "windows" {
|
||||
outputFile += ".exe"
|
||||
}
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
OutputType: "dev",
|
||||
Mode: build.Debug,
|
||||
Pack: false,
|
||||
Platform: runtime.GOOS,
|
||||
|
|
@ -294,6 +308,7 @@ func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, co
|
|||
Compiler: compilerCommand,
|
||||
OutputFile: outputFile,
|
||||
IgnoreFrontend: true,
|
||||
Verbosity: verbosity,
|
||||
}
|
||||
|
||||
return build.Build(buildOptions)
|
||||
|
|
|
|||
12
v2/internal/runtime/assets/assets.go
Normal file
12
v2/internal/runtime/assets/assets.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package assets
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed desktop_darwin.js
|
||||
var desktopDarwinJS string
|
||||
|
||||
//go:embed desktop_windows.js
|
||||
var desktopWindowsJS string
|
||||
|
||||
//go:embed wails.js
|
||||
var wailsJS string
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
# Assets Directory
|
||||
# Build Directory
|
||||
|
||||
The assets directory is used to house all the assets of your application.
|
||||
The build directory is used to house all the build files and assets for your application.
|
||||
|
||||
The structure is:
|
||||
|
||||
* bin - Output directory
|
||||
* dialog - Icons for dialogs
|
||||
* tray - Icons for the system tray
|
||||
* mac - MacOS specific files
|
||||
|
|
|
|||
|
|
@ -74,3 +74,11 @@ func RegenerateManifest(target string) error {
|
|||
}
|
||||
return a.CopyFile("windows/wails.exe.manifest", target, 0644)
|
||||
}
|
||||
|
||||
func RegenerateAppIcon(target string) error {
|
||||
a, err := debme.FS(assets, "build")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.CopyFile("appicon.png", target, 0644)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -337,16 +337,23 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if verbose {
|
||||
println("upx", "--best", "--no-color", "--no-progress", options.CompiledBinary)
|
||||
var args = []string{"--best", "--no-color", "--no-progress", options.CompiledBinary}
|
||||
|
||||
if options.CompressFlags != "" {
|
||||
args = strings.Split(options.CompressFlags, " ")
|
||||
args = append(args, options.CompiledBinary)
|
||||
}
|
||||
|
||||
output, err := exec.Command("upx", "--best", "--no-color", "--no-progress", options.CompiledBinary).Output()
|
||||
if verbose {
|
||||
println("upx", strings.Join(args, " "))
|
||||
}
|
||||
|
||||
output, err := exec.Command("upx", args...).Output()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error during compression:")
|
||||
}
|
||||
if verbose {
|
||||
println(output)
|
||||
println(string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ type Options struct {
|
|||
KeepAssets bool // Keep the generated assets/files
|
||||
Verbosity int // Verbosity level (0 - silent, 1 - default, 2 - verbose)
|
||||
Compress bool // Compress the final binary
|
||||
CompressFlags string // Flags to pass to UPX
|
||||
AppleIdentity string
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package build
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/buildassets"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
|
|
@ -27,9 +28,7 @@ func (d *DesktopBuilder) BuildAssets(options *Options) error {
|
|||
// Check assets directory exists
|
||||
if !fs.DirExists(options.ProjectData.BuildDir) {
|
||||
// Path to default assets
|
||||
defaultAssets := fs.RelativePath("./internal/assets")
|
||||
// Copy the default assets directory
|
||||
err := fs.CopyDir(defaultAssets, options.ProjectData.BuildDir)
|
||||
err := buildassets.Install(options.ProjectData.Path, options.ProjectData.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -108,7 +107,7 @@ func (d *DesktopBuilder) processApplicationIcon(assetDir string) error {
|
|||
// Copy default icon if one doesn't exist
|
||||
iconFile := filepath.Join(d.projectData.BuildDir, "appicon.png")
|
||||
if !fs.FileExists(iconFile) {
|
||||
err := fs.CopyFile(defaultIconPath(), iconFile)
|
||||
err := buildassets.RegenerateAppIcon(iconFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
# Assets Directory
|
||||
|
||||
The assets directory is used to house all the assets of your application.
|
||||
|
||||
The structure is:
|
||||
|
||||
* dialog - Icons for dialogs
|
||||
* tray - Icons for the system tray
|
||||
* mac - MacOS specific files
|
||||
* linux - Linux specific files
|
||||
* windows - Windows specific files
|
||||
|
||||
## Dialog Icons
|
||||
|
||||
Place any PNG file in this directory to be able to use them in message dialogs.
|
||||
The files should have names in the following format: `name[-(light|dark)][2x].png`
|
||||
|
||||
Examples:
|
||||
|
||||
* `mypic.png` - Standard definition icon with ID `mypic`
|
||||
* `mypic-light.png` - Standard definition icon with ID `mypic`, used when system theme is light
|
||||
* `mypic-dark.png` - Standard definition icon with ID `mypic`, used when system theme is dark
|
||||
* `mypic2x.png` - High definition icon with ID `mypic`
|
||||
* `mypic-light2x.png` - High definition icon with ID `mypic`, used when system theme is light
|
||||
* `mypic-dark2x.png` - High definition icon with ID `mypic`, used when system theme is dark
|
||||
|
||||
### Order of preference
|
||||
|
||||
Icons are selected with the following order of preference:
|
||||
|
||||
For High Definition displays:
|
||||
* name-(theme)2x.png
|
||||
* name2x.png
|
||||
* name-(theme).png
|
||||
* name.png
|
||||
|
||||
For Standard Definition displays:
|
||||
* name-(theme).png
|
||||
* name.png
|
||||
|
||||
## Tray
|
||||
|
||||
Place any PNG file in this directory to be able to use them as tray icons.
|
||||
The name of the filename will be the ID to reference the image.
|
||||
|
||||
Example:
|
||||
|
||||
* `mypic.png` - May be referenced using `runtime.Tray.SetIcon("mypic")`
|
||||
|
||||
## Mac
|
||||
|
||||
The `mac` directory holds files specific to Mac builds, such as `Info.plist`. These may be edited and used as part of the build.
|
||||
|
|
@ -61,11 +61,6 @@ func getBuildBaseDirectory(options *Options) (string, error) {
|
|||
return buildDirectory, nil
|
||||
}
|
||||
|
||||
// Gets the path to the default icon
|
||||
func defaultIconPath() string {
|
||||
return fs.RelativePath("internal/packager/icon1024.png")
|
||||
}
|
||||
|
||||
// Gets the platform dependent package assets directory
|
||||
func getPackageAssetsDirectory() string {
|
||||
return fs.RelativePath("internal/packager", runtime.GOOS)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package build
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/buildassets"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
|
@ -149,8 +150,7 @@ func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
|
|||
// Install default icon if one doesn't exist
|
||||
if !fs.FileExists(appIcon) {
|
||||
// No - Install default icon
|
||||
defaultIcon := fs.RelativePath("./internal/packager/icon1024.png")
|
||||
err = fs.CopyFile(defaultIcon, appIcon)
|
||||
err := buildassets.RegenerateAppIcon(appIcon)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package parser
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -11,6 +12,24 @@ import (
|
|||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
)
|
||||
|
||||
//go:embed index.template
|
||||
var indexTemplate string
|
||||
|
||||
//go:embed index.d.template
|
||||
var indexDTemplate string
|
||||
|
||||
//go:embed package.template
|
||||
var packageTemplate string
|
||||
|
||||
//go:embed package.d.template
|
||||
var packageDTemplate string
|
||||
|
||||
//go:embed globals.d.template
|
||||
var globalsDTemplate string
|
||||
|
||||
//go:embed package.json
|
||||
var packageJSON string
|
||||
|
||||
// GenerateWailsFrontendPackage will generate a Javascript/Typescript
|
||||
// package in `<project>/frontend/wails` that defines which methods
|
||||
// and structs are bound to your frontend
|
||||
|
|
@ -51,9 +70,8 @@ func (p *Parser) generateModule() error {
|
|||
}
|
||||
|
||||
// Copy the standard files
|
||||
srcFile := fs.RelativePath("./package.json")
|
||||
tgtFile := filepath.Join(moduleDir, "package.json")
|
||||
err = fs.CopyFile(srcFile, tgtFile)
|
||||
err = fs.CopyFile(packageJSON, tgtFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -102,11 +120,8 @@ func createBackendJSDirectory() (string, error) {
|
|||
|
||||
func generatePackage(pkg *Package, moduledir string) error {
|
||||
|
||||
// Get path to local file
|
||||
typescriptTemplateFile := fs.RelativePath("./package.d.template")
|
||||
|
||||
// Load typescript template
|
||||
typescriptTemplateData := fs.MustLoadString(typescriptTemplateFile)
|
||||
typescriptTemplateData := fs.MustLoadString(packageDTemplate)
|
||||
typescriptTemplate, err := template.New("typescript").Parse(typescriptTemplateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
|
|
@ -125,11 +140,8 @@ func generatePackage(pkg *Package, moduledir string) error {
|
|||
return errors.Wrap(err, "Error writing backend package file")
|
||||
}
|
||||
|
||||
// Get path to local file
|
||||
javascriptTemplateFile := fs.RelativePath("./package.template")
|
||||
|
||||
// Load javascript template
|
||||
javascriptTemplateData := fs.MustLoadString(javascriptTemplateFile)
|
||||
javascriptTemplateData := fs.MustLoadString(packageTemplate)
|
||||
javascriptTemplate, err := template.New("javascript").Parse(javascriptTemplateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
|
|
@ -154,11 +166,8 @@ func generatePackage(pkg *Package, moduledir string) error {
|
|||
|
||||
func generateIndexJS(dir string, packages []*Package) error {
|
||||
|
||||
// Get path to local file
|
||||
templateFile := fs.RelativePath("./index.template")
|
||||
|
||||
// Load template
|
||||
templateData := fs.MustLoadString(templateFile)
|
||||
templateData := fs.MustLoadString(indexTemplate)
|
||||
packagesTemplate, err := template.New("index").Parse(templateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
|
|
@ -183,11 +192,8 @@ func generateIndexJS(dir string, packages []*Package) error {
|
|||
}
|
||||
func generateIndexTS(dir string, packages []*Package) error {
|
||||
|
||||
// Get path to local file
|
||||
templateFile := fs.RelativePath("./index.d.template")
|
||||
|
||||
// Load template
|
||||
templateData := fs.MustLoadString(templateFile)
|
||||
templateData := fs.MustLoadString(indexDTemplate)
|
||||
indexTSTemplate, err := template.New("index.d").Parse(templateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
|
|
@ -213,11 +219,8 @@ func generateIndexTS(dir string, packages []*Package) error {
|
|||
|
||||
func generateGlobalsTS(dir string, packages []*Package) error {
|
||||
|
||||
// Get path to local file
|
||||
templateFile := fs.RelativePath("./globals.d.template")
|
||||
|
||||
// Load template
|
||||
templateData := fs.MustLoadString(templateFile)
|
||||
templateData := fs.MustLoadString(globalsDTemplate)
|
||||
packagesTemplate, err := template.New("globals").Parse(templateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue