Golang程序  |  239行  |  4.39 KB

// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"archive/zip"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type fileArg struct {
	relativeRoot, file string
}

type fileArgs []fileArg

func (l *fileArgs) String() string {
	return `""`
}

func (l *fileArgs) Set(s string) error {
	if *relativeRoot == "" {
		return fmt.Errorf("must pass -C before -f")
	}

	*l = append(*l, fileArg{*relativeRoot, s})
	return nil
}

func (l *fileArgs) Get() interface{} {
	return l
}

var (
	out          = flag.String("o", "", "file to write jar file to")
	manifest     = flag.String("m", "", "input manifest file name")
	directories  = flag.Bool("d", false, "include directories in jar")
	relativeRoot = flag.String("C", "", "path to use as relative root of files in next -f or -l argument")
	listFiles    fileArgs
	files        fileArgs
)

func init() {
	flag.Var(&listFiles, "l", "file containing list of .class files")
	flag.Var(&files, "f", "file to include in jar")
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: soong_jar -o jarfile [-m manifest] -C dir [-f|-l file]...\n")
	flag.PrintDefaults()
	os.Exit(2)
}

type zipWriter struct {
	time        time.Time
	createdDirs map[string]bool
	directories bool

	w *zip.Writer
}

func main() {
	flag.Parse()

	if *out == "" {
		fmt.Fprintf(os.Stderr, "error: -o is required\n")
		usage()
	}

	w := &zipWriter{
		time:        time.Now(),
		createdDirs: make(map[string]bool),
		directories: *directories,
	}

	// TODO: Go's zip implementation doesn't support increasing the compression level yet
	err := w.write(*out, listFiles, *manifest)
	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(1)
	}
}

func (z *zipWriter) write(out string, listFiles fileArgs, manifest string) error {
	f, err := os.Create(out)
	if err != nil {
		return err
	}

	defer f.Close()
	defer func() {
		if err != nil {
			os.Remove(out)
		}
	}()

	z.w = zip.NewWriter(f)
	defer z.w.Close()

	for _, listFile := range listFiles {
		err = z.writeListFile(listFile)
		if err != nil {
			return err
		}
	}

	for _, file := range files {
		err = z.writeRelFile(file.relativeRoot, file.file)
		if err != nil {
			return err
		}
	}

	if manifest != "" {
		err = z.writeFile("META-INF/MANIFEST.MF", manifest)
		if err != nil {
			return err
		}
	}

	return nil
}

func (z *zipWriter) writeListFile(listFile fileArg) error {
	list, err := ioutil.ReadFile(listFile.file)
	if err != nil {
		return err
	}

	files := strings.Split(string(list), "\n")

	for _, file := range files {
		file = strings.TrimSpace(file)
		if file == "" {
			continue
		}
		err = z.writeRelFile(listFile.relativeRoot, file)
		if err != nil {
			return err
		}
	}

	return nil
}

func (z *zipWriter) writeRelFile(root, file string) error {
	rel, err := filepath.Rel(root, file)
	if err != nil {
		return err
	}

	err = z.writeFile(rel, file)
	if err != nil {
		return err
	}

	return nil
}

func (z *zipWriter) writeFile(rel, file string) error {
	if s, _ := os.Stat(file); s.IsDir() {
		if z.directories {
			return z.writeDirectory(file)
		}
		return nil
	}

	if z.directories {
		dir, _ := filepath.Split(rel)
		err := z.writeDirectory(dir)
		if err != nil {
			return err
		}
	}

	fileHeader := &zip.FileHeader{
		Name:   rel,
		Method: zip.Deflate,
	}
	fileHeader.SetModTime(z.time)

	out, err := z.w.CreateHeader(fileHeader)
	if err != nil {
		return err
	}

	in, err := os.Open(file)
	if err != nil {
		return err
	}
	defer in.Close()

	_, err = io.Copy(out, in)
	if err != nil {
		return err
	}

	return nil
}

func (z *zipWriter) writeDirectory(dir string) error {
	for dir != "" && !z.createdDirs[dir] {
		z.createdDirs[dir] = true

		dirHeader := &zip.FileHeader{
			Name: dir,
		}
		dirHeader.SetMode(os.ModeDir)
		dirHeader.SetModTime(z.time)

		_, err := z.w.CreateHeader(dirHeader)
		if err != nil {
			return err
		}

		dir, _ = filepath.Split(dir)
	}

	return nil
}